Queue package for Meteor, for things like activity moderation


#1

Queue for Meteor

[Package link]

When you create an application, there are many times that you need to create a moderation system to approve certain requests, resolve complaints, and so on.

For example, let’s say we have an apartment listing website. We need to be able to receive reports of bad listings, and make the decision to pull them down or ignore the request. Alternatively, we might want to have a process to approve or reject them before publishing them to the website.

Queue can help you here with a simple server-side interface for creating various queues, adding items to them, and providing possible responses. How you expose that functionality to the rest of the applications would be completely up to.

Example Usage

To mirror our example above, lets say we want to create two queues: one to ensure that we have to approve every listing that goes on the website, and another to allow us to handle complaints. First, we need to define them and their possible tasks

Queue.register({
    reportListingProblem: {
        ignore: function (queueData, addedData) {
            // Rejected the document
            Listings.update(queueData._id, {
                $set: {
                    approved: -1
                }
            })

            // Send an email to notify the person
            MagicEmail.send({
                // ...
                message: addedData.message
            })
        }, 
        remove: function (queueData, addedData) {
            // Mark the document as approved
            Listings.update(queueData._id, {
                $set: {
                    approved: 1
                }
            })

            // Send an email to notify the person
            MagicEmail.send({
                // ...
                message: addedData.message
            })
        }
    }
})

Next, we need a function to add items to the queue. The function could run inside of a Method as the end developer would see fit.

Queue.add('requestPostApproval', {
    data: {
        userId: Meteor.userId(),
        listingId: listingId
    }
});

Finally, we would then have functions to let us access the items in these queues, and resolve them, such as:

Meteor.methods({
    resolveComplaint: function (itemId, personalMessage) {
        check([itemId, personalMessage], String);

        if (magicPermissionCheck()) {
            Queue.resolve(itemId, "approve", {
                message: personalMessage,
                resolvedBy: Meteor.user().name,
                resolvedId: Meteor.user()._id,
                resolvedAt: new Date()
            }) 
        }
    }
})

// ...

Meteor.call("resolveComplaint", "Cb3rLq6FNZu7QtqKE", "John - you're all set, thanks for using our app!", callback);

Then, the Queue would do its job. In our case, it would update a document and send an email indicating that that the operation was complete.


It would be great to know, what do you think of it - are there any features that you think are missing?


🍬 Introducing Meteor Candy, the Hackable Admin Dashboard
#2

To start things off, the first problem I see is, it may get messy if many people report the same thing. Perhaps Queue should group items that have the same data passed into them into one document? However, what should happen if a Queue item get resolved and then someone else submits again?

The other question is- do we want to make this tied to User Accounts, or should it work independently of the situation?


#3

Interesting package. I just implemented something similar for my website to allow flagging of content, albeit not as sophisticated :slight_smile:

What would the API look like to get information about queued listings so the UI can reflect it?


#4

In the most basic sense - it would provide some functions to access the MongoDB collection, or just provide direct access to it. That way, the developer can fit it to work into their own setup. They’d even be able to manage it in meteor shell.


#5

How would that work when the app is deployed, like to galaxy for example? I have never been able to figure that one out.


#6

I took a quick look - it looks like Galaxy, and many other vendors, do not support that. Make’s me think, maybe that is a gap Meteor Candy can fit, and across all hosting vendors.


In other news, the first version of the package is up and working. It would be great to get some opinions.


#7

That would be sweet. How would you make that secure, though?


#8

Just checked out the package and notice this

// Then, we want to remove the document to minimize the risk of it being called twice
remove = Queue.collection.remove(id);

wouldn’t that mean you can never have more than one action against a queue element? Maybe it would be better to track the action performed in the record, with a timestamp, and have an optional garbage collection, or expunge? In case you want to keep queue history?


#9

The same way any other method would be secured - through a permission check. In the simplest example of this, we can check the userId (Meteor.userId() === "Cb3rLq6FNZu7QtqKE").

I’m thinking, if someone can get past that, there is probably a much bigger problem.


#10

Indeed - each item would be resolved by one action. Since you can implement as many responses as you want, and pass in data into them, everything should be “resolvable” with one action. However, I’ve never built this kind of thing before, so maybe I am missing something important?

As for history - yes contemplating creating a second collection where resolved documents would go. It could also come with some kind of ability to clear documents, like if they are over 30 days old.


#11

Added a few more items:

  • automatically adds items to a “history” collection when the function runs successfully
  • started automatically tracking userIds when possible; they would be available if the item was ran by an authenticated user via Method
  • added resolveAll() for items that have the same data in them. the idea is, if you get 10 reports for the same post, you could resolve them all with one action

Ideas in consideration:

  • making history an optional feature
  • currently, you can pass data into the queue (i.e. Queue.add("reportPost", {...}) - but I’m thinking to split it up into two sets of data; identifier data, and “accessory” data, to make Queue.resolveAll() more effective
  • adding in some sort of priority system - i.e. if an item is reported many times, it probably needs to be addressed sooner
  • adding in some kind of alerting mechanism (for example, if there are more than 10 items in a queue, or one item had been reported more than 5 times, it would run a callback, such as sending an email or notification to admins)

#12

What about performing an edit to the apartment listing before approving it? I mentioned this in the Meteor Candy thread - most listing requests that come in will not be 100% perfect in terms of data integrity. Data will be missing, or have flaws (i.e. typos, minor errors), but that doesn’t mean it should be rejected. However, it should not be approved until the issues are fixed.

I visualize the ability for admins to approve, reject, or optionally edit before approving. But I’m not expecting my team to understand JSON in order to do it… they are non technical. This means we need to be able to put an interface on top of the mongo document that was submitted with input fields representing the various fields in the document. Some fields are numbers, other single line text, some multiline text, some date/time pickers, some dropdowns where you are choosing from a fixed set of options… the usual types of data input.

This is the meat and potatoes of what I’m looking for.

I also love the history idea. It would be great to be able to track on a per-page basis the admin actions that were taken against it, in case we need to possibly roll back or identify an erroneous change that was made at some point in time (and which admin made it for accountability!). I.e listing was submitted on X date, approved on Y date, edited on Z date (showing what content was edited, bonus points for showing a git-diff like interface where red shows what was removed and green shows what was added), and re-edited on Q, R, and S dates.

Being able to chronologically track the history and changes to your content would be massively valuable for our use case. But that’s a nice to have IMO, as I’m really in need of the edit functionality.


#13

The idea here is, you have two sets of data: the data passed in by the people who reported the item, and the data passed in by the person who resolved it.

With-in the Queue actions, you can write any logic you would like, and so the idea is that you would perform updates and what not through there.

[quote]I visualize the ability for admins to approve, reject, or optionally edit before approving. But I’m not expecting my team to understand JSON in order to do it… they are non technical. This means we need to be able to put an interface on top of the mongo document that was submitted with input fields representing the various fields in the document. Some fields are numbers, other single line text, some multiline text, some date/time pickers, some dropdowns where you are choosing from a fixed set of options… the usual types of data input.
[/quote]

Agree. The idea for Queue is be a boilerplate, so technically, you could access the MongoDB collection, send the data to the client, and display it as you wish. For people who do not wish to build their own admin tools, Meteor Candy will have a way of displaying the data with-in the panel.

Agree. I just pushed up a new README to describe the package intentions. One of the things I focused on here was grouping items (i.e. if one post has 10 complains, these should still be one item in the queue), and adding a priority system for urgent items.

The next thing under consideration, maybe we need some kind of triggers, like, if an item has more than 3 three reports, or a priority above 10, run a callback to notify someone from the company of this issue.

Link to new Readme

Finally, thanks for sharing your thoughts, please keep it coming :smile: