如何为每个集合插入添加时间戳,在firebase数据库的firebase函数中更新

时间:2018-10-05 08:00:35

标签: firebase google-cloud-firestore google-cloud-functions angularfire2

我有一个名为Posts的消防站集合 我在客户端插入了一个插件,就可以了。

我想使用firebase函数将createdAt和updatedAt字段添加到我的帖子集合Firestore中的每个插入中。

6 个答案:

答案 0 :(得分:4)

更新11/24/20 -我实际上将以下函数放入了我的npm包 adv-firestore-functions

请参阅:https://fireblog.io/blog/post/automatic-firestore-timestamps


我创建了一个通用云功能,可以使用createdAt和updatedAt时间戳更新所需的任何文档:

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;
    });


答案 1 :(得分:3)

要通过Cloud Function向createdAt记录添加Post时间戳,请执行以下操作:

exports.postsCreatedDate = functions.firestore
  .document('Posts/{postId}')
  .onCreate((snap, context) => {
    return snap.ref.set(
      {
        createdAt: admin.firestore.FieldValue.serverTimestamp()
      },
      { merge: true }
    );
  });

要向现有modifiedAt添加Post时间戳,可以使用以下代码。 但是,每次发布文档的某个字段发生更改(包括对createdAtupdatedAt字段的更改时,都会触发此Cloud功能,以无限循环 ....

exports.postsUpdatedDate = functions.firestore
  .document('Posts/{postId}')
  .onUpdate((change, context) => {
    return change.after.ref.set(
      {
        updatedAt: admin.firestore.FieldValue.serverTimestamp()
      },
      { merge: true }
    );
  });

因此,您需要比较文档的两种状态(即change.before.data()change.after.data(),以检测更改是否涉及的字段不是createdAtupdatedAt

例如,假设您的Post文档仅包含一个字段name(不考虑两个时间戳字段),则可以执行以下操作:

exports.postsUpdatedDate = functions.firestore
  .document('Posts/{postId}')
  .onUpdate((change, context) => {
    const newValue = change.after.data();
    const previousValue = change.before.data();

    if (newValue.name !== previousValue.name) {
      return change.after.ref.set(
        {
          updatedAt: admin.firestore.FieldValue.serverTimestamp()
        },
        { merge: true }
      );
    } else {
      return false;
    }
  });

换句话说,恐怕您必须逐字段比较两个文档状态。...

答案 2 :(得分:1)

您不需要云功能来执行此操作。如下所示,在客户端代码中设置服务器时间戳更为简单(且便宜):

self.textbuffer.insert(self.textbuffer.get_end_iter(), '\n' + str(values))

答案 3 :(得分:0)

这就是我用来防止Firebase Firestore无限循环的原因。onWrite触发器相比,我更喜欢将逻辑放在onUpdate
我使用npm软件包fast-deep-equal比较传入数据和先前数据之间的更改。

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

const equal = require('fast-deep-equal/es6');

export const notificationUpdated = functions.firestore
  .document('notifications/{notificationId}')
  .onWrite((change, context) => {
    // Get an object with the current document value.
    // If the document does not exist, it has been deleted.
    const document = change.after.exists ? change.after.data() : null;

    // Get an object with the previous document value (for update or delete)
    const oldDocument = change.before.data();

    if (document && !change.before.exists) {
      // This is a new document

      return change.after.ref.set(
        {
          createdAt: admin.firestore.FieldValue.serverTimestamp(),
          updatedAt: admin.firestore.FieldValue.serverTimestamp()
        },
        { merge: true }
      );
    } else if (document && change.before.exists) {
      // This is an update

      // Let's check if it's only the time that has changed.
      // I'll do this by making updatedAt a constant, then use `fast-deep-equal` to compare the rest
      const onlyTimeChanged = equal({ ...oldDocument, updatedAt: 0 }, { ...document, updatedAt: 0 });
      console.log(`Only time changed? ${onlyTimeChanged}`);
      if (onlyTimeChanged) {
        // The document has just been updated.
        // Prevents an infinite loop
        console.log('Only time has changed. Aborting...');
        return false;
      }
      return change.after.ref.set(
        {
          updatedAt: admin.firestore.FieldValue.serverTimestamp()
        },
        { merge: true }
      );
    } else if (!document && change.before.exists) {
      // This is a doc delete

      // Log or handle it accordingly
      return false;
    } else {
      return false;
    }
  });


希望这会有所帮助

答案 4 :(得分:0)

const after = change.after.data();
const before = change.before.data();
const check = Object.keys(after).filter(key => (key !== 'createdAt') && (key !== 'updatedAt')).map(key => after[key] != before[key]);
if (check.includes(true)) {
    return change.after.ref.set(
        {
            updatedAt: admin.firestore.FieldValue.serverTimestamp()
        },
        { merge: true }
    );
} else {
    return false;
}

答案 5 :(得分:0)

此解决方案支持第一级子集合,基于@Jonathan's answer above

**
 * writes fields common to root-level collection records that are generated by the
 * admin SDK (backend):
 * - createdAt (timestamp)
 * - updatedAt (timestamp)
 */
exports.createCommonFields = functions.firestore
.document('{colId}/{docId}')
.onWrite(async (change, context) => {
    // the collections you want to trigger
    const setCols = ['posts', 'reviews', 'comments', ];

    // run the field creator if the document being touched belongs to a registered collection
    if (setCols.includes(context.params.colId)) {
        console.log(`collection ${context.params.colId} is not registered for this trigger`);
        return null;
    } else {
        console.log(`running createCommonFields() for collection: ${context.params.colId}`);
    }

    // cause the creation of timestamp fields only
    _createCommonFields(change);
});

/**
 * createCommonFields' equivalent for sub-collection records
 */
exports.createCommonFieldsSubColl = functions.firestore
.document('{colId}/{colDocId}/{subColId}/{subColDocId}')
.onWrite(async (change, context) => {
    console.log(`collection: ${context.params.colId}, subcollection: ${context.params.subColId}`);

    // the subcollections of the collections you want to trigger
    // triggers for documents like 'posts/postId/versions/versionId, etc
    const setCols = {
        'posts': ['versions', 'tags', 'links', ], 
        'reviews': ['authors', 'versions'],
        'comments': ['upvotes', 'flags'],
    };

    // parse the collection and subcollection names of this document
    const colId = context.params.colId;
    const subColId = context.params.subColId;
    // check that the document being triggered belongs to a registered subcollection
    // e.g posts/versions; skip the field creation if it's not included
    if (setCols[colId] && setCols[colId].includes(subColId)) {
        console.log(`running createCommonFieldsSubColl() for this subcollection`);
    } else {
        console.log(`collection ${context.params.colId}/${context.params.subColId} is not registered for this trigger`);
        return null;
    }

    // cause the creation of timestamp fields
    _createCommonFields(change);
});

/**
 * performs actual creation of fields that are common to the
 * registered collection being written
 * @param {QueryDocumentSnapshot} change a snapshot for the collection being written
 */
async function _createCommonFields(change) {
    // 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 = change.after.exists ? change.after.data() : null;
    const before = 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;
    }

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