firebase functions + realtime database - database structure and code - javascript

I'm on a simple application:
It's my first try with firebase functions + realtime database.
The functions are going to be called by an external client application (e.g.: android).
if it was not javascript + nosqldb I would not have a problem, but here I am stuck, because I'm not sure for the best db structure and about transaction-like operations.
I. stored data:
user profile (id, fullname, email, phone, photo)
tickets amount per user
history for tickets buyings
history for tickets usings
II. actions:
user buy some tickets - should add tickets to the user's amount AND add a record to buyings history
user use some tickets - should remove tickets from the user's amount AND add a record to usings history
So my base problem is this ANDs - if it was a SQL db i would use a transaction but here I'm not sure what is db structure and js code in order to achieve same result.
EDITED:
======== index.js =======
exports.addTickets = functions.https.onCall((data, context) => {
// data comes from client app
const buyingRecord = data;
console.log(‘buyingRecord: ‘ + JSON.stringify(buyingRecord));
return tickets.updateTicketsAmmount(buyingRecord)
.then((result)=>{
tickets.addTicketsBuyingRecord(buyingRecord);
result.userid = buyingRecord.userid;
result.ticketsCount = buyingRecord.ticketsCount;
return result;
});
});
====== tickets.js =======
exports.updateTicketsAmmount = function(buyingRecord) {
var userRef = db.ref(‘users/’ + buyingRecord.userid);
var amountRef = db.ref(‘users/’ + buyingRecord.userid + ‘/ticketsAmount’);
return amountRef.transaction((current)=>{
return (current || 0) + buyingRecord.ticketsCount;
})
.then(()=>{
console.log(“amount updated for userid [“ + buyingRecord.userid + “]”);
return userRef.once(‘value’);
})
.then((snapshot)=>{
var data = snapshot.val();
console.log(“data for userid [“ + snapshot.key + “]:” + JSON.stringify(data));
return data;
});
}
exports.addTicketsBuyingRecord = function(buyingRecord) {
var historyRef = db.ref(‘ticketsBuyingHistory’);
var newRecordRef = historyRef.push();
return newRecordRef.set(buyingRecord)
.then(()=>{
console.log(‘history record added.’);
return newRecordRef.once(‘value’);
})
.then((snapshot)=>{
var data = snapshot.val();
console.log(‘data:’ + JSON.stringify(data));
return data;
});
}

You would have to use the callback, the request on Android to add or read data have an onSuccess or OnFailure CallBack, i used this to trigger my new request.
You can check this on the doc here :)
Also if instead of using RealTime Database you use Firestore you can use the FireStore Transactions here is the info.

Related

How do I reject writing value to Firebase if have the same value in my DB? (Javascript)

I'm creating my custom order id with auto-increment generator function for my project. I will state my question here, if you want to know the whole story please read below.
As written in the title, I need a way to reject my set to Firebase and it has to be done in 1 query. Currently, it will write my orderID to Firebase without rejecting it. But I need to reject if there is the same ID in the table.
The short version of my code will be posted here, the whole function will be posted below.
firebase.database().ref('orderCounter/orderIDsChecker/'+orderID).set({
id: orderID,
}, function(error) {
if (error) {
console.log('Order ID fail to generate. Regenerating new ID')
createOrderID(orderCounterRef);
} else {
console.log('Order ID created!')
}
});
}
The story,
I'm creating my own custom order id with auto-increment generator function for my project. The problem is that if multiple users creating order at the same time, it will generate the same id. Yes, I can use transaction() to solve the problem but I have no idea how to use it. Therefore, I have created my own version of the "transaction". With my method, I am able to prevent duplicates id unless 2 or more users create order within 1 second of gap. Or if anyone is kind enough to show me an example of how to write a transaction for my function, I thank you in advance.
The flow of the code is,
Get "currentMonth" and "orderIdCounter" from Firebase -> orderIdCounter +1 and update to Firebase -> start the process of generating order id -> Send the generated id to firebase -> If return success "order ID created", If not "got duplicate id" Re-run the whole process.
Below is the code for my order id generator function.
function createOrderID(orderCounterRef){
var childData = [];
var orderID;
//Get the Current Month and Order ID Counter from Firebase
orderCounterRef.on('value', function(snap) { childData = snapshotToArrayWithoutID(snap); });
var currentMonth = childData[0];
var orderIDCounter = childData[1];
if (orderIDCounter !== undefined){
//Update orderIDCounter on Firebase.
//This is to prevent duplicate orderID when multiple users is creating order at the same time.
var IDCounter = parseInt(orderIDCounter) + 1;
//Set IDCounter to 3 digits
IDCounter = ('00' + IDCounter.toString()).slice(-3);
firebase.database().ref('orderCounter/orderIDCounter').set(IDCounter);
//Handle the process to generate Order ID. Return in YYMMxxx(auto increment) format.
orderID = handleCreateOrderID(currentMonth, (parseInt(orderIDCounter) - 1));
//Check if duplicate ID on firebase
firebase.database().ref('orderCounter/orderIDsChecker/'+orderID).set({
id: orderID,
}, function(error) {
if (error) {
console.log('Order ID fail to generate. Regenerating new ID')
createOrderID(orderCounterRef);
} else {
console.log('Order ID created!')
}
});
}
return orderID;
}
My DB:
You should indeed use a transaction as you have mentioned in your question.
The following should do the trick:
//Declare a function that increment a counter in a transaction
function createOrderID() {
var orderIdRef = firebase.database().ref('orderId');
return orderIdRef.transaction(function(currentId) {
return currentId + 1;
});
}
//Call the asynchronous createOrderID() function
createOrderID().then(function(transactionResult) {
console.log(transactionResult.snapshot.val());
});
If you want to start the counter at a specific value, just create an orderId node in your database and assign a specific value to it, e.g; 1912000.
If you just want to start at 1, you don't need to create a node, it will be automatically created with the first call to the createOrderID() function.
Thank you, #samthecodingman & #Renaud Tarnec for your advice.
I took #samthecodingman's code and change a bit to fit my project. But I use generateOrderID() only to call the result and it works well. But you won't get any value with just the code. I call out another function (connectToFirebase) whenever users enter the page. I am not sure why it works or if this is the right way, but it works for me and that's good enough.
export function generateOrderID(){
var orderId;
var childData = [];
const orderCounterRef = firebase.database().ref('orderCounter/');
//Get the Current Month from Firebase
orderCounterRef.on('value', function(snap) { childData = snapshotToArrayWithoutID(snap); });
//Check ID format YYMMXXX (XXX=auto_increment). Hanlde auto_increment for Year and Month
handleOrderIdFormat(childData[0], orderCounterRef)
//transaction
orderCounterRef.child('orderId').transaction(function(currentId) {
orderId = (currentId||0) +1;
return orderId;
}, function(err) {
if( err ) {
console.log(err)
}
});
return orderId;
}
export function connectToFirebase(){
//Connection Firebase Database
const orderCounterRef = firebase.database().ref('orderCounter/');
orderCounterRef.on('value', function(snap) { });
}

Returning a value from callback function

I am using the on() method to retrieve a data snapshot in our database, but I need to be able to store this snapshot value so that I can use it to retrieve another separate snapshot.
Here is what our database looks like:
Firebase Real-Time Database
There is a node for users and a separate node for devices. Each user has a child "devices" which is a list of devices associated with that user. The user that I have expanded only has one device.
What I am trying to do is store this deviceID, and then do a separate query to find that device in the "Devices" node. Here is what my code looks like:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
ref.on("value", getData);
And then the callback function looks like this:
function getData(data)
{
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
}
which is just grabbing the first device in the users device list and printing it to the console. I am trying to figure out how to
return this value so that I can use it when getting the data from the Devices tree. Which, I'm guessing,
would look something like this:
let deviceRef = database.ref("devices/").child(retrievedValue);
deviceRef.on("value", getData2);
Where retrievedValue is the deviceID that I got from the first query.
Is it possible to do this in javascript, or is there a better way? I know similar questions have already been asked, but I've found all the examples I've seen online to be really confusing and not very helpful for me. Any help at all would be super appreciated because I am kind of stuck on this. Thanks!
You have to learn about promises and asynchronous programming. Here are two ways to do what you want:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
ref.once("value").then((data) {
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
let deviceRef = database.ref("devices/").child(currentDevice);
return deviceRef.once("value");
}).then((value) {
console.log("value is " + value);
})
or with async/await:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
let data = await ref.once("value")
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
let deviceRef = database.ref("devices/").child(currentDevice);
let value = await deviceRef.once("value");
console.log("value is " + value);
I'm more confident about the second one as I'm typing these without testing.
These links would be helpful to start learning this stuff:
https://firebase.googleblog.com/2016/01/keeping-our-promises-and-callbacks_76.html
https://firebase.google.com/docs/functions/terminate-functions
Edit: I fixed the code above by replacing on with once. However now this is not listening to changes in the db anymore. To correct your code to listen to user's device changes:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
ref.on("value", getData);
function getData(data) // may need to place this before the code above
{
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
let deviceRef = database.ref("devices/").child(currentDevice);
// no need to listen to this, as a change in one device would fire
// for every user. you probably don't want that.
deviceRef.once("value", (data) {
console.log(data);
});
}
In order to achieve that, you have to modify your callback as following:
function getData(data, callback)
{
currentDevice = Object.keys(data.val())[0];
console.log("current device: " + currentDevice);
callback(currentDevice)
}
Then we you call your callback from within the code, do it like this:
let uid = fireBaseUser.uid;
//get a reference to the database
let database = firebase.database();
let ref = database.ref("users/").child(uid).child("devices");
ref.on("value", getData((this_is_the_value_from_inside_callback) => {
console.log(`your value: ${this_is_the_value_from_inside_callback}`)
});
You can also try to run this little snippet (I used PlayCode), to see it more friendly testing environment
somefunction = (data, callback) => {
console.log(`data: ${data}`)
data += 100
console.log(`data: ${data}`)
callback(data)
}
somefunction(100, (dataReturned) => {
console.log(`data returned: ${dataReturned}`)
})

Retrieve multiple data from firebase database in one cloud function

I am faced with the problem of retrieving two data values of a single node from my firebase database and reference it in my javascript file but don't know how to go about it. I have been able to retrieve just one data value from a node (in this case "message") but I would like to add "from" as well. Most tutorials just reference one so I am really confused. So how do I get multiple data values?
This is my code...
JS file
exports.sendNotification7 = functions.database.ref('/GroupChat/{Modules}/SDevtChat/{SDevtChatId}/message')
.onWrite(( change,context) =>{
// Grab the current value of what was written to the Realtime Database.
var eventSnapshot = change.after.val();
var str = "New message from System Development Group Chat: " + eventSnapshot;
console.log(eventSnapshot);
var topic = "Management.Information.System";
var payload = {
data: {
name: str,
click_action: "Student_SystemsDevt"
}
};
// Send a message to devices subscribed to the provided topic.
return admin.messaging().sendToTopic(topic, payload)
.then(function (response) {
// See the MessagingTopicResponse reference documentation for the
// contents of response.
console.log("Successfully sent message:", response);
return;
})
.catch(function (error) {
console.log("Error sending message:", error);
});
});
You can read from however many nodes you want in a Cloud Function. However, only one can trigger the function to run.
To read from your database use the following code:
admin.database().ref('/your/path/here').once('value').then(function(snapshot) {
var value = snapshot.val();
});
You will probably want to read from the same place that the Cloud Function was triggered. Use context.params.PARAMETER to get this information. For the example you posted your code would turn out looking something like this:
admin.database().ref('/GroupChat/'+context.params.Modules+'/SDevtChat/'+context.params.SDevtChatId+'/from').once('value').then(function(snapshot) {
var value = snapshot.val();
});
Just trigger your function one level higher in the JSON:
exports.sendNotification7 =
functions.database.ref('/GroupChat/{Modules}/SDevtChat/{SDevtChatId}')
.onWrite(( change,context) =>{
// Grab the current value of what was written to the Realtime Database.
var eventSnapshot = change.after.val();
console.log(eventSnapshot);
var str = "New message from System Development Group Chat: " + eventSnapshot.message;
var from = eventSnapshot.from;
...

Searching Through Firebase for Data

I am trying to get data out of my firebase for a specific user via the realtime database. Whenever a user signs up and new piece is added to the stripe_customers piece of the database. How can I get the customer_id for each customer?
Current Customer -
var database = firebase.database();
var userId = firebase.auth().currentUser.uid;
Current Database Layout - Database Layout
Thanks!
I am not sure whether I understand you clear, but it seems it is quite easy something like:
var database = firebase.database();
var userId = firebase.auth().currentUser.uid;
var dbRef = firebase.database().ref('stripe_customer/' + userId + '/customer_id');
dbRef.once('value', snapshot=> {
if (snapshot.exists()){
var custumerId = snapshot.val().customer_id;
_goAheadWithCustomerId(custumerId);
}
})
_goAheadWithCustomerId(c){
console.log('Customer Id is :', c);
}

Firebase total user count

Is there a way to get all the users' count in firebase? (authenticated via password, facebook, twitter, etc.) Total of all social and email&password authenticated users.
There's no built-in method to do get the total user count.
You can keep an index of userIds and pull them down and count them. However, that would require downloading all of the data to get a count.
{
"userIds": {
"user_one": true,
"user_two": true,
"user_three": true
}
}
Then when downloading the data you can call snapshot.numChildren():
var ref = new Firebase('<my-firebase-app>/userIds');
ref.once('value', function(snap) {
console.log(snap.numChildren());
});
If you don't want to download the data, you can maintain a total count using transactions.
var ref = new Firebase('<my-firebase-app>');
ref.createUser({ email: '', password: '', function() {
var userCountRef = ref.child('userCount');
userCountRef.transaction(function (current_value) {
// increment the user count by one
return (current_value || 0) + 1;
});
});
Then you can listen for users in realtime:
var ref = new Firebase('<my-firebase-app>/userCount');
ref.on('value', function(snap) {
console.log(snap.val());
});
Using Cloud Functions:
exports.updateUserCount = functions.auth.user().onCreate(user => {
return admin.database().ref('userCount').transaction(userCount => (userCount || 0) + 1);
});
Just note that a Cloud Functions event is not triggered when a user is created using custom tokens. In that case, you would need to do something like this:
exports.updateUserCount = functions.database.ref('users/{userId}').onCreate(() => {
return admin.database().ref('userCount').transaction(userCount => (userCount || 0) + 1);
});
Update 2021
I stumbled on this question and wanted to share three methods to get total number of signed-up users.
👀 Looking in the console
Go to the console, under Authentication tab, you can directly read the number of users under the list of users:
56 users! yay!
📜 Using the admin SDK
For programmatic access to the number of users with potential filter on provider type, registration date, last connection date... you can write a script leveraging listUsers from the admin SDK.
For example, to count users registered since March 16:
const admin = require("firebase-admin");
const serviceAccount = require("./path/to/serviceAccountKey.json");
admin.initializeApp({ credential: admin.credential.cert(serviceAccount) });
async function countUsers(count, nextPageToken) {
const listUsersResult = await admin.auth().listUsers(1000, nextPageToken);
listUsersResult.users.map(user => {
if (new Date(user.metadata.creationTime) > new Date("2021-03-16T00:00:00")) {
count++;
}
});
if (listUsersResult.pageToken) {
count = await countUsers(count, listUsersResult.pageToken);
}
return count;
}
countUsers(0).then(count => console.log("total: ", count));
💾 Storing users in a DB
Your app maybe already stores user documents in Firestore, or the Realtime Database, or any other database. You can count these records to get the total number of registered users. (If you use Firestore, you can read my article on how to count documents)
Its mid 2022 now, and as far as I can tell, the required capability is still not the Node.js admin SDK, but it is available from the identity toolkit REST api.
The suggestion from Louis Coulet of looking at the Firebase console is what tipped me off. Looking at the console's API calls, we can see there is a "query" endpoint that can return the number of accounts.
The endpoint is documented here : https://cloud.google.com/identity-platform/docs/reference/rest/v1/projects.accounts/query
The admin SDK can provide the required access token to call the endpoint. See https://firebase.google.com/docs/reference/admin/node/firebase-admin.app.credential.md#credentialgetaccesstoken
firebase.initializeApp();
firebase.app().options.credential.getAccessToken().then(the_token => ...)
As the console does, we provide an empty query expression and set the returnUserInfo flag to false
curl --request POST \
--url 'https://identitytoolkit.googleapis.com/v1/projects/your_project_goes_here/accounts:query?alt=json' \
--header 'Content-Type: application/json' \
--header 'authorization: Bearer the_token' \
--data '{
"returnUserInfo": false,
"expression": []
}'
The query result is the number of accounts
{
"recordsCount": "1223"
}
Here is a javascript Module for this purpose - https://gist.github.com/ajaxray/17d6ec5107d2f816cc8a284ce4d7242e
In single line, what it does is -
Keep list (and count) of online users in a Firebase web app - by isolated rooms or globally
For counting all users using this module -
firebase.initializeApp({...});
var onlineUsers = new Gathering(firebase.database());
gathering.join();
// Attach a callback function to track updates
// That function will be called (with the user count and array of users) every time user list updated
gathering.onUpdated(function(count, users) {
// Do whatever you want
});

Categories