0
votes

I'm writing some Node.js code to import data from an existing database into Firebase Cloud Firestore. We have createdAt and updatedAt dates that I want preserved in the transition. The problem is we've created Cloud Functions to automatically set those on object creation, thus it's always overwriting the object's value with the current timestamp. If I run our import script again, thus set is updating the existing document, the createdAt and updatedAt values in the object are preserved.

How can I make it use the specified values and not overwrite them with the current timestamp on create?

const oldNote = {
  id: "someid",
  text: "sometext",
  createdAt: new Date(2018, 11, 24, 10, 33, 30, 0),
  updatedAt: new Date(2018, 11, 24, 10, 33, 30, 0)
}

const note = {
  text: oldNote.text,
  createdAt: firebase.firestore.Timestamp.fromDate(oldNote.createdAt),
  updatedAt: firebase.firestore.Timestamp.fromDate(oldNote.updatedAt)
};
firestoredb.collection("notes").doc(oldNote.id).set(note).then((docRef) => {
    //FIXME: createdAt and updatedAt aren't preserved, always today
}).catch((error) => {
    console.error("Error setting user: ", error);
});

Here's the Cloud Functions:

exports.updateCreatedAt = functions.firestore
.document("{collectionName}/{id}")
.onCreate((snap, context) => {
    const now = admin.firestore.FieldValue.serverTimestamp();

    return snap.ref.set(
        {
            createdAt: now,
            updatedAt: now
        },
        { merge: true }
    );
});

exports.updateUpdatedAt = functions.firestore
.document("{collectionName}/{id}")
.onUpdate((change, context) => {
    const newValue = change.after.data();
    const previousValue = change.before.data();

    if (
        Boolean(newValue.updatedAt) &&
        Boolean(previousValue.updatedAt) &&
        newValue.updatedAt.isEqual(previousValue.updatedAt)
    ) {
        const now = admin.firestore.FieldValue.serverTimestamp();

        return change.after.ref.set({ updatedAt: now }, { merge: true });
    } else {
        return false;
    }
});
2
What exactly is dateobj? Please spare no details. We should be able to take your code and run it for ourselves. - Doug Stevenson
@DougStevenson It's a JavaScript date object. This is just an example, the old note is queried from a different database. - Jordan H
The exact values matter. - Doug Stevenson

2 Answers

0
votes

As you can see from the API documentation for Timestamp.fromDate() takes a Date object and converts it to a Timestamp object. However, you're passing it a Timestamp object (unless your edit is faking values that didn't come from the database write in Cloud Functions). This is an unnecessary conversion, and it's likely just returning the current time instead of what you expect. Just use the timestamp values in place:

const note = {
  text: oldNote.text,
  createdAt: oldNote.createdAt,
  updatedAt: oldNote.updatedAt
};

Bear in mind that when you write a Date into Firestore, it stores a Timestamp internally. It's not going to come back out as a Date.

0
votes

I went ahead and deployed an update to the onCreate trigger in the Google Cloud Console. It now checks if the object already contains createdAt and/or updatedAt and uses them instead of overwriting them if so.

exports.updateCreatedAt = functions.firestore
.document("{collectionName}/{id}")
.onCreate((snap, context) => {
    const now = admin.firestore.FieldValue.serverTimestamp();
    const createdAt = snap.data().createdAt != undefined ? snap.data().createdAt : now;
    const updatedAt = snap.data().updatedAt != undefined ? snap.data().updatedAt : now;

    return snap.ref.set(
        {
            createdAt: createdAt,
            updatedAt: updatedAt
        },
        { merge: true }
    );
});