Is there any way to auto increment a field in firebase - javascript

I want to auto increment a stock number for each new item added to the firestore database. (Not using the push() method)
The stock number is a controlled number and needs to be unique and incremented by 1 every time a new field is added without me having to manually specify the stock number.
Please keep in mind - im a beginner and do not have in depth knowledge of firebase.

You can do it easily using Cloud Functions
// Change '/COLLECTION/{DOC}' to which document do you want to increment the counter when it's created
exports.counter = functions.firestore.document('/COLLECTION/{DOC}').onCreate((snap, context) => {
const db = admin.firestore();
// Change 'counter/ref' to where do you want to increment
const countRef = db.doc('counter/ref');
return db.runTransaction(t => {
return t.get(countRef).then(doc => {
const counter = (doc.data().counter || 0) + 1;
t.update(countRef, {counter: counter});
});
});
});
You can learn more about Cloud Functions here https://firebase.google.com/docs/functions/

Related

Firestore orderBy Timestamp object

I am trying to order a query by timestamp.
In my document I have a field called "date" which has this form:
date = {
nanoseconds: 963000000,
seconds: 1594917688
}
In my code I have this:
let photosArray = [];
firebase
.getDatabase()
.collection("photos")
.doc(firebase.getCurrentUser().uid)
.collection("userPhotos")
.orderBy("date", "asc") // Sorted by date in ascending direction
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === "added") {
// Get the new photo
const photo = change.doc.data();
// Add the photo to the photos list
photosArray.push(photo);
}
});
// The last photo is at the top of the list
setPhotos(photosArray);
But when I render the list of photos, they are unsorted... For example: the first one taken 2 hours ago, the second one taken 1 minute ago, and the last one taken 2 years ago.
UPDATE
This is how I store the date in firestore
Firebase.js:
getTimestamp = () => firebase.firestore.FieldValue.serverTimestamp();
PhotoUploader.js
await firestore
.collection("photos")
.doc(userId)
.collection("userPhotos")
.add({
id,
date: firebase.getTimestamp(),
});
If your date field shows a map with two nested fields, that is not really a timestamp, and it won't sort the way you expect. You should take a look at the code that adds the date field to the document, and make sure it uses a timestamp correctly. Either that, or use a single timestamp numeric value that will sort the way you expect.

How to get the number of documents under a firestore collection? [duplicate]

This question already has answers here:
Cloud Firestore collection count
(29 answers)
How to get count of documents in a collection? [duplicate]
(2 answers)
Closed 2 years ago.
I want to get the total number of documents inside a firestore collection, I'm making a forum app, so I want to show the current amount of comments inside each discussion.
There's something like db.collection("comments").get().lenght or something like that?
With the size property of the QuerySnapshot, you can get the number of documents of a collection, as follows:
db.collection("comments").get().then(function(querySnapshot) {
console.log(querySnapshot.size);
});
HOWEVER, you should note that this implies that you read all the documents of the collection each time you want to get the number of documents and, therefore, it has a cost.
So, if your collection has a lot of documents, a more affordable approach would be to maintain a set of distributed counters that hold the number of documents. Each time you add/remove a document, you increase/decrease the counters.
Based on the documentation, here is how to do for a write:
First, initialize the counters:
const db = firebase.firestore();
function createCounter(ref, num_shards) {
let batch = db.batch();
// Initialize the counter document
batch.set(ref, { num_shards: num_shards });
// Initialize each shard with count=0
for (let i = 0; i < num_shards; i++) {
let shardRef = ref.collection('shards').doc(i.toString());
batch.set(shardRef, { count: 0 });
}
// Commit the write batch
return batch.commit();
}
const num_shards = 3; //For example, we take 3
const ref = db.collection('commentCounters').doc('c'); //For example
createCounter(ref, num_shards);
Then, when you write a comment, use a batched write as follows:
const num_shards = 3;
const ref = db.collection('commentCounters').doc('c');
let batch = db.batch();
const shard_id = Math.floor(Math.random() * num_shards).toString();
const shard_ref = ref.collection('shards').doc(shard_id);
const commentRef = db.collection('comments').doc('comment');
batch.set(commentRef, { title: 'Comment title' });
batch.update(shard_ref, {
count: firebase.firestore.FieldValue.increment(1),
});
batch.commit();
For a document deletion you would decrement the counters, by using: firebase.firestore.FieldValue.increment(-1)
Finally, see in the doc how to query the counter value!

Vue computed property overwriting global state without vuex

I have a list of people who have scores. In state I have them listed in an array, one of the items in the array is 'scoreHistory' which is an array of objects containing their scores at different points in time. I want to filter this set for different time periods i.e. -5 days, -30 days so instead of just seeing the overall score I can see the scores if everyone started at 0 say 30 days ago.
I have it (kind of) working. See my code below:
filteredScores () {
if(!this.people) {
return
}
// Here I was trying to ensure there was a copy of the array created in order to not change the original array. I thought that might have been the problem.
let allPeople = this.people.slice(0) // this.people comes from another computed property with a simple getter. Returns an array.
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
for (const p of allPeople ) {
let filteredScores = inf.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
p.scoreHistory=filteredScores
//calculate new score
p.score = inf.scoreHistory.reduce(function(sum,item) {
return sum + item.voteScore
},0)
}
return allInf
}
I expected it to return to me a new array where each person's score is summed up over the designated time period. It seems to do that OK. The problem is that it is altering the state that this.people reads from which is the overall data set. So once it filters all that data is gone. I don't know how I am altering global state without using vuex??
Any help is greatly appreciated.
Your problem isn't that you're modifying the array, but that you're modifying the objects within the array. You change the scoreHistory and score property of each item in the array. What you want to do instead is create a new array (I recommend using map) where each item is a copy of the existing item plus a new score property.
filteredScores () {
if(!this.people) {
return
}
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
return this.people.map(p => {
let filteredScores = p.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
//calculate new score
let score = filteredScores.reduce(function(sum, item) {
return sum + item.voteScore
}, 0)
// Create a new object containing all the properties of p and adding score
return {
...p,
score
}
}
})

I want to get the average my firebase data

I want to average the related values ​​when the data in the FireBase is updated.
I am using Firebase functions and can not load data.
I can change the data I want when the event occurs, but I can not calculate the average of the data.
exports.taverage = functions.database.ref('/User/tsetUser/monthQuit/{pushId}')
.onCreate((snapshot, context) => {
const promiseRoomUserList = admin.database().ref('/User/tsetUser/monthQuit/{pushId}').once('value');
var sum=0;
const arrayTime = [];
snapshot.forEach(snapshot => {
arrayTime.push('/User/tsetUser/monthQuit/{pushId}'.val());
})
for(let i=0; i<arrayTime.length; i++){
sum+=arrayTime[i];
}
return admin.database().ref('/User/tsetUser/inform/standardQuit').set(sum);
});
//I Want 'standardQuit' value set average.
I'm not sure why you can't calculate the average, but a simpler version of your code would be:
exports.taverage = functions.database.ref('/User/tsetUser/monthQuit/{pushId}')
.onCreate((snapshot, context) => {
return admin.database().ref('/User/tsetUser/monthQuit/{pushId}').once('value')
.then(function(snapshot) {
let sum=0;
snapshot.forEach(child => {
sum = sum + child.val();
})
let avg = sum / snapshot.numChildren();
return admin.database().ref('/User/tsetUser/inform/standardQuit').set(avg);
});
});
The biggest differences:
This code returns promises from both the top-level, and the nested then(). This is needed so Cloud Functions knows when your code is done, and it can thus stop billing you (and potentially shut down the container).
We simply add the value of each child to the sum, since you weren't using the array in any other way. Note that the child.val() depends on your data structure, which you didn't share. So if it fails there, you'll need to update how you get the exact value (or share you data structure with us).
The code actually calculates the average by dividing the sum by the number of child nodes.
Consider using a moving average
One thing to keep in mind is that you're now reading all nodes every time one node gets added. This operation will get more and more expensive as nodes are added. Consider if you can use a moving average, which wouldn't require all child nodes, but merely the current average and the new child node. The value will be an approximate average where more recent value typically have more weight, and is much cheaper to calculate:
exports.taverage = functions.database.ref('/User/tsetUser/monthQuit/{pushId}')
.onCreate((snapshot, context) => {
return admin.database().ref('/User/tsetUser/inform/standardQuit').transaction(function(avg) {
if (!avg) avg = 0;
return (15.0 * avg + snapshot.val()) / 16.0;
});
});

Query for average scores of all game results in a collection?

My app stores game results that can have a final score in the range of -30 to +30 in a field called score. How do I query for the overall average of all the game results?
Simple Solution
If you know the number of game results being written will be at most once per second, you can use Cloud Functions to update a separate document average/score. For each game result addition, if the document didn't exist, set a field called count to 1 and a field called score to the game score. If the document did exist, add 1 to the field called count and add the score to the field called score.
Now, to query for the average score, simply read average/score and divide score by count.
Scalable Solution
If you suspect or know the number of game results being written will exceed once per second, you will need to apply a distributed counter style of the Simple Solution.
Your data model for the average document will use subcollections and look like:
// average/score
{
"num_shards": NUM_SHARDS,
"shards": [subcollection]
}
// average/score/shards/${NUM}
{
"count": 115,
"score": 1472
}
To make your update code more streamlined, you can initialize these shards first with:
// ref points to db.collection('average').doc('score')
function createAverageAggregate(ref, num_shards) {
var batch = db.batch();
// Initialize the counter document
batch.set(ref, { num_shards: num_shards });
// Initialize each shard with count=0
for (let i = 0; i < num_shards; i++) {
let shardRef = ref.collection('shards').doc(i.toString());
batch.set(shardRef, { count: 0, count: 0 });
}
// Commit the write batch
return batch.commit();
}
Updating the average aggregate in Cloud Functions is now as easy as:
// ref points to db.collection('average').doc('score')
function updateAverage(db, ref, num_shards) {
// Select a shard of the counter at random
const shard_id = Math.floor(Math.random() * num_shards).toString();
const shard_ref = ref.collection('shards').doc(shard_id);
// Update count in a transaction
return db.runTransaction(t => {
return t.get(shard_ref).then(doc => {
const new_count = doc.data().count + 1;
const new_score = doc.data().score + 1;
t.update(shard_ref, { count: new_count, score: new_score });
});
});
}
Getting the average can then be done with:
// ref points to db.collection('average').doc('score')
function getAverage(ref) {
// Sum the count and sum the score of each shard in the subcollection
return ref.collection('shards').get().then(snapshot => {
let total_count = 0;
let total_score = 0;
snapshot.forEach(doc => {
total_count += doc.data().count;
total_score += doc.data().score;
});
return total_score / total_count;
});
}
The write rate you can achieve in this system is NUM_SHARDS per second, so plan accordingly. Note: You can start out small and increase the number of shards easily. Simply create a new version of createAverageAggregate to increase the number of shards by first initializing the new ones, then updating the num_shards setting to match. This should be automatically picked up by your updateAverage and getAverage functions.

Categories

Resources