Document creation timestamp (server-time) in Firestore - javascript

I'm new to firebase and firestore, creating a single-page app, I was wondering how can I store the document creation time with the document, representing the server-side time. When I changed my system time to an incorrect time and then ran the code below, it was storing the same (incorrect) timestamp for both clientDate and serverTimestamp fields.
I tried the following (Firebase JavaScript SDK version 9.0.2):
import { addDoc, collection, Timestamp } from 'firebase/firestore';
import { db } from '../index';
export const storeSomething = async () => {
const data = {
clientDate: new Date(),
serverTimestamp: Timestamp.now(),
};
const someCol = collection(db, 'somecollection');
const docRef = await addDoc(someCol, data);
};

The Timestamp.now() function returns the current client-side time, so is indeed sensitive to clock skew and manipulation.
To write a server-side timestamp, use:
in v9/modular syntax: serverTimestamp()
in v8/compat syntax: firebase.firestore.FieldValue.serverTimestamp()
in the Admin SDK: admin.firestore.FieldValue.serverTimestamp()

Related

Cloud Firestore subcollection syntax in Web version 9 API

I'm trying to learn Firestore Cloud 9 using a tutorial written for Web version 8. I am stuck trying to refactor what appears to be a subcollection of some sort.
The Web 8 code looks like this:
const ref = firestore.collection('users').doc(auth.currentUser.uid).collection('posts');
const query = ref.orderBy('createdAt');
const [querySnapshot] = useCollection(query);
My (failed) attempt looks something like this:
import { firestore, auth, serverTimestamp } from "#/lib/firebase";
import { collection, getDocs, orderBy, query } from "firebase/firestore";
import { useRouter } from "next/router";
import { useCollection } from "react-firebase-hooks/firestore";
const ref = collection(firestore, 'users');
const q = query(
ref,
where('username', '==', auth.currentUser.uid),
orderBy('createdAt', 'desc')
);
//const newPosts = (await getDocs(q)).docs.map((doc) => doc.data());
const [querySnapshot] = useCollection(q);
I can get the first collection without issue. However grabbing the subcollection (?) via the doc(ument) isn't something I can figure out. I've tried getDocs() (commented out, above) as well as getDoc() & .doc.
This line in the v8 and before syntax:
const ref = firestore.collection('users').doc(auth.currentUser.uid).collection('posts');
Will look like this in v9 and later:
const ref = collection(firestore, 'users', auth.currentUser.uid, 'posts');

is there any way to find a document from firebase firestore with the query of where

I have a collection of tokens in which each document create with auto id but I store a tokenId in the document and now I want to search a single document which has specific tokenId
How can I implement where query in my this code
const docRef = doc(db , "tokens")
const data= await getDoc(docRef);
First, you should use collection() instead of doc() to create a CollectionReference. Then you can build the required Query using query() with where() as shown below:
import { collection, getDocs, query, where } from "firebase/firestore"
const colRef = collection(db , "tokens")
const qSnap = await getDocs(query(colRef, where("tokenId", "==", "TOKEN_VALUE")));
if (qSnap.size) {
const data = qSnap.docs[0].data();
} else {
console.log("No token found")
}
Also checkout:
Firestore: What's the pattern for adding new data in Web v9?
Perform simple and compound queries in Cloud Firestore

Reducing Firebase SDK bundle size?

I've used the new tree-shakeable version of Firebase SDK, which still seems to produce quite a large chunk, considering what I use it for.
I only use the Firestore part, and have actually moved all Firestore operations to the backend, the only thing I'm doing on the front end is initiating the Firebase app instance and starting a snapshot listener (due to the real-time ability), yet the bundle produced is still 275kb large.
import { initializeApp } from 'firebase/app'
import { getFirestore, collection, doc, onSnapshot, query, where, orderBy } from 'firebase/firestore'
const documents = ref(null)
const firebaseApp = initializeApp(firebaseConfig)
const db = getFirestore(firebaseApp)
const ref = query(collection(db, 'documents'), where(`something`, '==', 'something'), orderBy('createdAt', 'desc'))
const unsub = onSnapshot(ref, (snap) => {
let results = []
snap.docs.forEach(el => {
el.data().createdAt && results.push({ ...el.data(), id: el.id })
})
documents.value = results
}, err => {
console.log(err.message)
documents.value = null
})
Is there a way to reduce that size slightly, without sacrificing the real-time ability?

How to get a Subcollection inside a Collection in Firestore Web Version 9 (Modular)?

I was using the chaining mode of the Firestore Web 8, but I'm in the way of updated it to Module 9 and have been a hard time trying to figure out how to get all the content of my subcollection (collection inside my collection).
My older function is like this and works fine:
function getInfo(doc_name) {
let infoDB = db
.collection("collection_name")
.doc(doc_name)
.collection("subcollection_name")
.get();
return alunoHistorico;
}
so with the module way I tried this code
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const docRef = doc(db, "collection_name", "doc_name");
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log("Document data:", docSnap.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
but the function doc() expects a even arguments (not counting the db argument) so if I try to use with 3 arguments like this, I get a error:
const docRef = doc(db, "collection_name", "doc_name", "subcollection_name");
to it work I have to pass the exactly document that is inside the subcollection
const docRef = doc(db, "collection_name", "doc_name", "subcollection_name", "sub_doc");
but it doesn't work for me because I have a list os docs inside the subcollection, that I want o retrieve.
So how can I get all my docs inside my subcollection?
Thanks to anyone who take the time.
You need to use collection() to get a CollectionReference instead of doc() which returns a DocumentReference:
const subColRef = collection(db, "collection_name", "doc_name", "subcollection_name");
// odd number of path segments to get a CollectionReference
// equivalent to:
// .collection("collection_name/doc_name/subcollection_name") in v8
// use getDocs() instead of getDoc() to fetch the collection
const qSnap = getDocs(subColRef)
console.log(qSnap.docs.map(d => ({id: d.id, ...d.data()})))
I wrote a detailed answer on difference between doc() and collection() (in V8 and V9) here:
Firestore: What's the pattern for adding new data in Web v9?
If someone want to get realtime updates of docs inside sub collection using onSnapshot in Modular Firebase V9, you can achieve this like:
import { db } from "./firebase";
import { onSnapshot, collection } from "#firebase/firestore";
let collectionRef = collection(db, "main_collection_id", "doc_id", "sub_collection_id");
onSnapshot(collectionRef, (querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log("Id: ", doc.id, "Data: ", doc.data());
});
});

Why are my JavaScript Date/Time objects stored as maps as opposed to timestamps in Cloud Firestore?

The attached image is a screenshot of two "date/time" entries in my Firestore document.
timeCompleted is a value that I entered directly in the database, choosing "timestamp" as the field's type. Per the screenshot, you can tell that the date/time shows in a readable format (not that it matters to me).
timeCreated is a value added by my JavaScript Reactjs application, using firebase.firestore.Timestamp.fromDate(new Date(myDate + " " + myTime)), as prescribed by the Google docs. As opposed to being stored as a timestamp type, it is stored as a map containing nanoseconds and seconds.
Is there a way to store my JavaScript date (timeCreated) as a true timestamp, similarly to timeCompleted?
My firebase class (gets imported through React's Context API)
import app from "firebase/app";
import "firebase/firestore";
import "firebase/functions";
const config = {
apiKey: ...,
authDomain: ...,
databaseURL: ...,
projectId: ...
};
class Firebase {
constructor() {
app.initializeApp(config);
this.auth = app.auth();
this.firestore = app.firestore;
this.functions = app.functions;
}
}
export default Firebase;
My React Component
import React, { useState, useEffect, useContext } from "react";
import { FirebaseContext } from "../../Firebase";
const InnerComponent = React.memo(props => {
const firebase = useContext(FirebaseContext);
//Calls Google Cloud function below
firebase.functions().httpsCallable("storeInDb")({
const myDate = "2019-02-25";
const myTime = "17:45";
orderDetails {
name: "someName",
timeCreated: firebase.firestore.Timestamp.fromDate(new Date(myDate + " " + myTime))
}
)}
My Google Cloud Function
exports.storeInDb = functions.https.onCall(async (data, context) => {
return id = await orderCreation(data.orderDetails);
});
const orderCreation = async orderDetails => {
try {
const docRef = await admin.firestore()
.collection(pathToCollection...)
.add(orderDetails);
return docRef.id;
} catch (error) {
console.log("ORDER CREATION ERROR", error);
}
};
It looks like you're expecting the type of the Timestamp object that you're passing into the callable function to be retained between the client and server. Unfortunately, it doesn't work that way. When a Timestamp object is serialized, it just converts to a JavaScript object with seconds and nanoseconds properties. The callable function on the other side isn't going to automatically reconstitute that object into a Timestamp. In fact, all types are lost, and everything is just converted to JSON.
What you're going to have to do is take that serialized object in the function and turn it back into a Timestamp yourself before you pass it to the Firestore SDK. Then, the SDK will arrange for Firestore to save it as a timestamp type field. This means you're going to have to do some initial processing on the data parameter and make sure it has all the correct values to write into Firestore.
Again, if it's not completely clear - the type of the object must be correct with the Firestore SDK calls. You can't pass arbitrary objects and expect it to do smart conversions.

Categories