Firebase db.ref is not a function (node.js) - node.js

Can someone please tell me what is wrong in my code before I go back to MongoDB?
Project is in Node.js (Next.js)
This is how I set firebase (it works for authentication with Google Login for instance):
import { initializeApp } from 'firebase/app';
const credentials = {
...
}
const firebase = initializeApp(credentials);
export default firebase;
then this is my api js file where it throws error "db.ref" is not a function:
import firebase from '#/firebase/firebase'
import { getDatabase, ref, onValue, update, child, orderByChild, equalTo, once } from "firebase/database"
export default async (req, res) => {
const db = getDatabase(firebase);
if (req.method === 'POST') {
const body = req.body
const playlistTracks = body.playlist
const playlistName = body.name
const uid = body.uid
const data = ...
console.log(data)
var ref = db.ref().child('users');
ref.child(uid).orderByChild('n').equalTo(playlistName).once("child_added", function(snapshot) {
let listId = snapshot.key;
db.ref("users/" + uid + "/" + listId).update(data);
res.send({ risp : 'ok' })
});
}
}
realtime database structure is:
- users
- <user uid>
- <playlist uid>
c: []
n: "playlist name"
so I'm trying to first retrieve the correct playlist by it's name ("n" value) comparing all "n" with the name of the given playlist, then I'd need to update (overwrite) it with my object (data)
UPDATE:
So I found the other methods Web version 9 (modular) in the documentation, as suggested by Frank van Puffelen below, but it now thorws a error
#firebase/database: FIREBASE WARNING: Exception was thrown by user
callback. Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they
are sent to the client
My code now is like this:
try {
const myQuery = query(ref(db, 'users/' + uid), orderByChild('n'), equalTo(playlistName));
onChildAdded(myQuery, (data) => {
let listId = data.key;
const updates = {};
updates["users/" + uid + "/" + listId] = dataToUpdate;
update(ref(db), updates);
}, {
onlyOnce: true
});
res.send({ risp : 'ok' })
} catch (e) {
res.status(400).end();
}
also tried like this, but it's the same error:
const myQuery = query(ref(db, 'users/' + uid), orderByChild('n'), equalTo(playlistName));
onChildAdded(myQuery, (data) => {
let listId = data.key;
update(ref(db, "users/" + uid + "/" + listId), dataToUpdate)
.then(() => {
res.send({ risp: 'ok' })
})
.catch((error) => {
res.status(400).end();
});
}, {
onlyOnce: true
});

You're using the new modular API, so can't use namespaced accessors like db.ref() anymore. Instead use ref(db, 'users').
I highly recommend keeping the documentation handy while upgrading this code to the new API version, as it has example of the modular and namespaced API side by side. The upgrade guide is probably also a worthwhile read).

Related

Correctly fetch authentication tokens server-side in a node.js React app hosted on Cloud Run

While not a front-end developer, I'm trying to set up a web app to show up a demo for a product. That app is based on the Sigma.js demo app demo repository.
You'll notice that this app relies on a graph which is hosted locally, which is loaded as:
/src/views/Root.tsx :
useEffect(() => {
fetch(`${process.env.PUBLIC_URL}/dataset.json`)
.then((res) => res.json())
.then((dataset: Dataset) => {...
// do things ....
and I wish to replace this by a call to another service which I also host on Cloud Run.
My first guess was to use the gcloud-auth-library, but I could not make it work - especially since it does not seem to support Webpack > 5 (I might be wrong here), the point here this lib introduces many problems in the app, and I thought I'd be better off trying the other way GCP suggests to handle auth tokens: by calling the Metadata server.
So I replaced the code above with:
Root.tsx :
import { getData } from "../getGraphData";
useEffect(() => {
getData()
.then((res) => res.json())
.then((dataset: Dataset) => {
// do even more things!
getGraphData.js :
import { getToken } from "./tokens";
const graphProviderUrl = '<my graph provider service URL>';
export const getData = async () => {
try {
const token = await getToken();
console.log(
"getGraphData.js :: getData : received token",
token
);
const request = await fetch(
`${graphProviderUrl}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await request.json();
console.log("getGraphData.js :: getData : received graph", data);
return data;
} catch (error) {
console.log("getGraphData.js :: getData : error getting graph data", error);
return error.message;
}
};
tokens.js :
const targetAudience = '<my graph provider service base URL>'; // base URL as audience
const metadataServerAddress = "169.254.169.254"; // use this to shortcut DNS call to metadata.google.internal
export const getToken = async () => {
if (tokenExpired()) {
const token = await getValidTokenFromServer();
sessionStorage.setItem("accessToken", token.accessToken);
sessionStorage.setItem("expirationDate", newExpirationDate());
return token.accessToken;
} else {
console.log("tokens.js 11 | token not expired");
return sessionStorage.getItem("accessToken");
}
};
const newExpirationDate = () => {
var expiration = new Date();
expiration.setHours(expiration.getHours() + 1);
return expiration;
};
const tokenExpired = () => {
const now = Date.now();
const expirationDate = sessionStorage.getItem("expirationDate");
const expDate = new Date(expirationDate);
if (now > expDate.getTime()) {
return true; // token expired
}
return false; // valid token
};
const getValidTokenFromServer = async () => {
// get new token from server
try {
const request = await fetch(`http://${metadataServerAddress}/computeMetadata/v1/instance/service-accounts/default/token?audience=${targetAudience}`, {
headers: {
'Metadata-Flavor': 'Google'
}
});
const token = await request.json();
return token;
} catch (error) {
throw new Error("Issue getting new token", error.message);
}
};
I know that this kind of call will need to be done server-side. What I don't know is how to have it happen on a React + Node app. I've tried my best to integrate good practices but most questions related to this topic (request credentials through a HTTP (not HTTPS!) API call) end with answers that just say "you need to do this server-side", without providing more insight into the implementation.
There is a question with similar formulation and setting here but the single answer, no upvote and comments is a bit underwhelming. If the actual answer to the question is "you cannot ever call the metadata server from a react app and need to set up a third-party service to do so (e.g. firebase)", I'd be keen on having it said explicitly!
Please assume I have only a very superficial understanding of node.js and React!

Caching in NODEJS

I am trying to cache the results of the Twitter query as they have rate limiting and I cannot seem to figure it out.
I am following this tutorial here.
The Cache.js file looks like so (taken from tutorial):
import NodeCache from 'node-cache';
class Cache {
constructor(ttlSeconds) {
this.cache = new NodeCache({ stdTTL: ttlSeconds, checkperiod: ttlSeconds * 0.2, useClones: false });
}
get(key, storeFunction) {
const value = this.cache.get(key);
if (value) {
return Promise.resolve(value);
}
return storeFunction().then((result) => {
this.cache.set(key, result);
return result;
});
}
del(keys) {
this.cache.del(keys);
}
delStartWith(startStr = '') {
if (!startStr) {
return;
}
const keys = this.cache.keys();
for (const key of keys) {
if (key.indexOf(startStr) === 0) {
this.del(key);
}
}
}
flush() {
this.cache.flushAll();
}
}
export default Cache;
My Twitter query file looks like so :
import TwitterCount from "../models/TwitterModel.js";
import { TwitterApi } from 'twitter-api-v2';
import dotenv from "dotenv";
dotenv.config();
import CacheService from '../middleware/Cache.js';
const twitterClient = new TwitterApi(process.env.TWITTER_API_BEARER_TOKEN);
const readOnlyClient = twitterClient.readOnly;
const ttl = 60 * 60 * 1; //cache for 1 hour
const cache = new CacheService(ttl);
export const getCount = async(req, res) => {
try {
const twit = await TwitterCount.findOne({
attributes:['followersCount']
});
const key = `Twit_${String(twit.dataValues.followersCount)}`;
const twitterFollowers = await readOnlyClient.v2.followers('1563787278857785350'); //HejOfficial account Twitter
const results = cache.get( key , () => twitterFollowers.data.length );
if (twit.dataValues.followersCount === results) {
console.log('same results');
} else {
await TwitterCount.create({
followersCount: results
});
console.log("Twitter Data added Successful");
}
res.json(twit);
cache.del(key);
} catch (error) {
console.log(error);
}
}
I presume that I am not using the key properly. Please assist if you have encountered this issue before, or provide a better caching alternative.
Thank you.
Try to use the account id as the key, rather than follower count. This way you are caching the accounts followers for an hour.
Something like this should get you going with the cache, it doesn't have the database inserts, but they can be handled now in the getCount method.
export const getCount = async(req, res) => {
const result = await getCountFromAPI("1563787278857785350") //HejOfficial account Twitter
res.json(result);
};
const getCountFromAPI = async(account) => {
const key = `Twit_${account}`;
return cache.get(key, async() => {
const twitterFollowers = await readOnlyClient.v2.followers(account);
return twitterFollowers;
});
};
Note that if you save the results to your database every time, it defeats the purpose of the cache. I'd suggest only using the API for this data, and if you want to use database as well, maybe the cache is unnessesary? Save the follower count and account id with time-updated value, and if time updated is more than one hour ago, then query the value again from Twitter.

how to save an user profile picture in nodejs (upload an image in mongodb with nodejs)

I'm building a Nodejs application with a user profile update. All user information updates perfectly except for the image. I am not able to save the image neither locally nor in the database. Could someone help me solve this?
This is my code
For the controller
const {createUser, getUserPerUsername, searchUsersPerUsername, addUserIdToCurrentUserFollowing,
removeUserIdToCurrentUserFollowing, findUserPerId, updateUser, deleteUser} = require('../queries/users.queries')
const request = require('request')
const directoryUploads = require('../path').directoryUploads
const path = require('path')
const fs = require('fs')
const upload = require('../Middleware/upload')
exports.userUpdate = async (req, res, next ) => {
const userId = req.params.userId
const user = await findUserPerId(userId)
const body = req.body
try {
if(user) {
let picture = req.files['picture'] ? req.files['picture'][0].filename : null
console.log("Picture : " +picture)
const user = await findUserPerId(userId)
console.log("User Old : " + user)
if (userOld.picture) {
if (picture) {
const pathImagePreview = path.join(directoryUploads, 'uploads/') + user.picture
fs.exists(pathImagePreview, exists => {
if (exists)
fs.unlinkSync(pathImagePreview)
})
} else {
picture = user.picture
}
}
const userUptd = await updateUser(userId, {
firstname: body.firstname,
lastname: body.lastname,
"local.email": body.email,
phones: body.phones,
picture: body.picture
})
console.log("Image uploaded successfully !!")
req.flash('success', [`User ${userUptd.username} updated!`])
res.redirect('/users/profile')
} else {
throw new Error('Please make sure to fill in the fields correctly!')
}
} catch (error) {
console.log(error)
req.flash('errors', [error.message])
req.flash('body', body)
}
}
here picture refers to the name of the input type="file".
The error returned is "Cannot read property 'picture ' of undefined". I think that it's probably linked to the file retrieving
i think you have an undeclared variable :
userOld is undefined right ?
also this line is never used so i'm not sure if you really handled the upload :
const upload = require('../Middleware/upload')
do you use multer ?

Cloud Functions Error Firebase

I am trying to do push notification through Functions in Firebase.
Here is my code in Node.JS
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendPushNotification = functions.database.ref('Received Downs/{owner}/{propID}')
.onCreate(event => {
// get the owner name and propID
var owner = event.params.owner;
var propID = event.params.propID;
// Log it
console.log('Owner: ' + owner + ' Property ID: ' + propID);
// Get the list of device notification tokens.
return admin.database().ref(`/users/${owner}`).once('value', snapshot => {
var ownerID = snapshot.val();
// This will find requester ID
return admin.database().ref(`/Received Downs/${owner}/${propID}`).once('value', snapshot => {
// First will find the property the requester downed
var property = snapshot.val();
// Find the requester's name
return admin.database().ref('/Users Info/' + property.downedBy).once('value', snapshot => {
// Requester's ID
var downedBy = snapshot.val();
// Notification details.
const payload = {
notification: {
title: 'You have a new request!',
body: `${downedBy.name} is now following you.`,
sound: 'default'
}
};
// Listing all tokens. (the function save the keys of given variable)
// const tokens = Object.keys(getDeviceTokens.val());
// var fcmToken = "dzJLM-JdIt8:APA91bHBJJP6t3Z0_T7kEFDrLLsu5T_NpYsR6QmJz2EJhpK88SV1ZfemoyCtC_6hl3_0sCPdzkvlQFoAFhlWn4xTQOY3k5P8JMvdYFyeNBN1lHceQtytE0y-9oTP6qgKspi9p9E8V9dB";
// Send to all tokens of a device
admin.messaging().sendToDevice(ownerID.token, payload)
.then(response => {
console.log("Successfully sent message:", response);
}).catch(function(error) {
console.log("Error sending message:", error);
});
})
})
})
})
And here is what I got in LOGS at Firebase Functions
When I used a variable that has fem token , typed, it works fine, but not when i fetched from Firebase Realtime Database. Anyone could tell me why?
The problem I had the wrong path return admin.database().ref(/users/${owner})

Why is Cloud Functions for Firebase taking 25 seconds?

For clarity I have other cloud functions that all run intermittently (i.e from 'cold' in around 2-6 seconds, and all use the same boilerplate set up of importing an admin instance and exporting the function as a module)
I've seen other similar posts but this is really bugging me. I have a cloud function like so:
const admin = require('../AdminConfig');
const { reportError } = require('../ReportError');
module.exports = (event) => {
const uid = event.params.uid;
const snapshot = event.data;
if (snapshot._newData === null ) {
return null;
}
console.log('Create org begin running: ', Date.now());
const organisation = event.data.val();
const rootRef = admin.database().ref();
const ref = rootRef.child('/organisations').push();
const oid = ref.key;
const userData = {
level: 'owner',
name: organisation.name,
};
const orgShiftInfo = {
name: organisation.name,
startDay: organisation.startDay || 'Monday',
};
const updatedData = {};
updatedData[`/users/${uid}/currentOrg`] = oid;
updatedData[`/users/${uid}/organisations/${oid}`] = userData;
updatedData[`/organisations/${oid}`] = organisation;
updatedData[`/org_shift_info/${oid}`] = orgShiftInfo;
rootRef.update(updatedData, (err) => {
if (err) {
return rootRef.child(`/users/${uid}/addOrgStatus`).set({ error: true })
.then(() => {
console.log(`error adding organisation for ${uid}: `, err);
return reportError(err, { uid });
});
}
console.log('Create org wrote succesfully: ', Date.now());
return rootRef.child(`/users/${uid}/addOrgStatus`).set({ success: true });
});
}
I understand the 'cold start' thing but I think something is seriously wrong that it's taking 25 seconds. The logs don't return any error and are as so:
Is there some deeper way I can debug this to try and figure out why it's taking so long? It's unusable at the moment. Thanks a lot.
Solved:
Sorry,
I misunderstood the API a bit. I should have watched the promise video first!
I needed to put
return rootRef.update...
instead of
rootRef.update...

Resources