loading...
Cover image for Community-driven data modifications using Cloud Functions

Community-driven data modifications using Cloud Functions

kylewelsby profile image Kyle Welsby ・4 min read

Prelude

I'm working on a side project around my paid client work and weekly Soulection Tracklists publications called TracksPlayed (a spin-off from Soulection Tracklists).

With this new project, I'm exploring new technologies and processes to improve features I wish I had in Soulection Tracklists.

Here is an insight into a process that I think will allow the community to suggest changes to data (I'm also open to suggestions in the comment section if you have other solutions).

For this; will assume you're using Firebase FireStore (a real-time NoSQL database by Google), but can easily be adapted to work with MongoDB and PostgreSQL projects.

Video Preview

Suggestions Collection

Let's start off with a database collection called suggestions here we will have a structure like the following.

{
  "path": "string",
  "key": "string",
  "value": "string",
  "createdAt": "datetime",
  "approvedAt": "datetime",
  "committedAt": "datetime"
}

Let's take a moment to explain each part of this schema.

property type description
path String A reference to the document we want to change for example, product/1, the path will contain the collection and document ID so we can find it later.
key String The value within the document, we will want to change the given value. Let's say a someone wants to suggest a name change to product/1 the key would be name which leads us to;
value String The content that is suggested to be changed to.
createdAt String Optional, but useful to know when the suggestion was created.
approvedAt DateTime A date and time when the suggestion is approved. We'll explain this a bit later.
committedAt DateTime Once the change has been applied we'll mark this with the time the action was applied and finished. Useful to know the change has been firmly committed, or allow us to re-run the job safely.

Suggestions Moderation

Depending on your use case you can have a site admin moderation view or a per account moderation view which would be scoped to only the suggestions of the path match any of the documents the account has access to.

I'm not going to cover the per account moderation view in order to keep this writing short. Feel free to explore this yourself.

We'll assume a simple administration view where we will fetch all documents in the suggestions collection that has a null in the approvedAt column.

Approving the suggestion will set the approvedAt value to the current date new Date().

Handling Suggestions

On Firebase we can set a Firebase Function to listen for changes in FireStore when we update a document. In MongoDB or PostgreSQL you can add triggers, and execute a Cloud Function/Lambda.

We'll aim to do to the following in the Cloud Function;

  1. Set the value of the suggested path key to the value given.
  2. Set the committedAt to the current time.
  3. Ensure we do not handle the approval multiple times

Start with a simple function to listen to the suggestions documents.

exports.onSuggestionApproved = functions.firestore.document('suggestions/{documentId}')
  .onUpdate((snap, context) => {
    // ... do something when the document is updated
  })
})

setting the value of the suggestion

We'll use the path to get the document reference and can set the key and value.

let ref = db.doc(data.path)
ref.set({
  [data.key]: data.value,
}, { merge: true })

Finishing the function

The ref.set returns a Promise, and we will have two promises to wait for. We need to add them to an array and wait for them all to resolve before completing the function.

Change each instance of ref.set to push to a promises array

let promises = []
promises.push(
  ref.set(/* ... */)
)

Return once all the promises are successfully resolved.

return Promise.all(promises)

Putting it all together

exports.onSuggestionApproved = functions.firestore.document('suggestions/{documentId}')
  .onUpdate((snap, context) => {
    const before = snap.before.data()
    const data = snap.after.data()
    let promises = []
    /**
     * @todo add approvedBy to ensure the changing document can be modified by a member of the account
     */
    if (!before.approvedAt && data.approvedAt) {
      let ref = db.doc(data.path)
      promises.push(
        ref.set({
        [data.key]: data.value,
        }, { merge: true })
      )
      promises.push(
        snap.after.ref.set({
          committedAt: new Date()
        }, { merge: true })
      )
    }
    return Promise.all(promises)
  })

Now we have a simple Cloud Function that listens for changes in a Firestore document and will trigger changes in a corresponding document once the approvedAt is defined.

Video Preview

Notable Concerns

In the final code, you can see a @todo. The power of this function allows any changes to any document. To avoid people setting themselves as administrators or committing changes to documents they have not authorisation on, we need to add a permission check.
This is a conditional check that would have to be made before the document is changed.

Conclusion

Allowing your community to make changes to database documents allows a whole wealth of crowd-sourced information.
This example allows the functionality without having to enable or replicate the code for each place we want to implement the feature on.

Having a suggestions column allows changes to be staged and moderated before they are committed. Also retains a history of the submitted changes for auditing should that be of interest to you.

If you have any better solutions or questions please comments below.

Posted on by:

kylewelsby profile

Kyle Welsby

@kylewelsby

Code Wrangler, Travel & Food Photographer, and Maker of http://soulectiontracklists.com . From Great Britain 🇬🇧

Discussion

markdown guide