Handling Firestore Timestamps
5 min read

In Firestore, there are two ways to deal with timestamps safely. If you pass a regular date value on the client side, it can be tampered with. We want our data to be secure.

Firestore Functions

I don't recommend this version, but I am posting it for reference. You should usually just use Firestore Rules (keep reading). I created a Universal Function a while back, which Could and Should be simplified:

exports.myFunction = functions.firestore
    .document('{colId}/{docId}')
    .onWrite(async (change, context) => {

        // the collections you want to trigger
        const setCols = ['posts', 'reviews','comments'];

        // if not one of the set columns
        if (setCols.indexOf(context.params.colId) === -1) {
            return null;
        }

        // simplify event types
        const createDoc = change.after.exists && !change.before.exists;
        const updateDoc = change.before.exists && change.after.exists;
        const deleteDoc = change.before.exists && !change.after.exists;

        if (deleteDoc) {
            return null;
        }
        // simplify input data
        const after: any = change.after.exists ? change.after.data() : null;
        const before: any = change.before.exists ? change.before.data() : null;

        // prevent update loops from triggers
        const canUpdate = () => {
            // if update trigger
            if (before.updatedAt && after.updatedAt) {
                if (after.updatedAt._seconds !== before.updatedAt._seconds) {
                    return false;
                }
            }
            // if create trigger
            if (!before.createdAt && after.createdAt) {
                return false;
            }
            return true;
        }

        // add createdAt
        if (createDoc) {
            return change.after.ref.set({
                createdAt: admin.firestore.FieldValue.serverTimestamp()
            }, { merge: true })
                .catch((e: any) => {
                    console.log(e);
                    return false;
                });
        }
        // add updatedAt
        if (updateDoc && canUpdate()) {
            return change.after.ref.set({
                updatedAt: admin.firestore.FieldValue.serverTimestamp()
            }, { merge: true })
                .catch((e: any) => {
                    console.log(e);
                    return false;
                });
        }
        return null;
    });

This function automatically adds the createdAt and updatedAt timestamps to the documents in a the collections you specify. I honestly would not recommend using this, as Universal Functions can be more costly than specifying the collections you want to use. Nevertheless, you could see the pattern here to get the results you want.

Also, look at my old adv-firestore-functions repo if you want more ideas.

Firestore Rules + serverTimestamp

This is by far the better way. Firestore Functions will incur more reads and writes than necessary. What is better, is to simply check on the security with Firestore Rules as you add the data.

import { serverTimestamp } from 'firebase/firestore';
...
await setDoc(doc(db, "some-doc")), {
  something: 'here',
  updatedAt: serverTimestamp()
});

Notice I do not use new Date(), but instead serverTimestamp();

Then, just make sure your Firestore rules match the case:

allow create: if request.time == request.resource.data.createdAt;
allow update: if request.time == request.resource.data.updatedAt;

This is enforce proper timestamps. If you try to add an incorrect date, it will fail.

J

cloud-firestore
cloud-functions
firebase
timestamp


Related Posts

9 min read



15 min read


© 2023 Code.Build