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.
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;
- Set the value of the suggested
path
key
to thevalue
given. - Set the
committedAt
to the current time. - 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.
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.
Top comments (0)