Setting up a Strapi photo filter for NSFW photos was surprisingly easy

Strapi NSFW Photo Filter (using DeepAI)

April 11th 2021

Recommended Reading

Strapi Plugins

The strapi plugin system is surprisingly easy to use and access. By default there is a upload plugin that is bundled with a standard strapi install. The strapi-plugin-upload code can be found here.

Safe Upload API Strategy

The upload API by default is found by POSTing to /upload. This creates a File model instance and sets it up in the database.

In order to filter images, we need a way to intercept the upload requests and submit the image to an AI platform like DeepAI's NSFW content filter.

To my knowledge is not an easy way to customize the code for an external plugin like strapi-plugin-upload. The code is bundled in node_modules and it provides no extension hooks.

However, if we create a completely new route, say /upload-safe and then use that, we can then just use this new endpoint in our client instead of the default one. Then, in that endpoint we simply call the internal controller for the default upload plugin and upload the image to get the url. Then we can submit the url to DeepAI and if the results are unsatisfactory, we can just tell the upload plugin to remove the image and return a status message to the client saying the image was unsatisfactory.

Solution

Make sure to npm i -s form-data node-fetch first.

Create a new api, like so called upload-safe:

/api
  /upload-safe
    /config
      routes.json
    /controllers
      upload-safe.js

routes.json contains:

{
  "routes": [
    {
      "method": "POST",
      "path": "/upload-safe",
      "handler": "upload-safe.upload",
      "config": {
        "policies": [],
        "description": "Upload a file and reject NSFW photos",
        "tag": {
          "plugin": "upload",
          "name": "File"
        }
      }
    }
  ]
}

upload-safe.js will contain something like this:

'use strict';
const FormData = require('form-data');
const fetch = require('node-fetch');
/**
 * Read the documentation (https://strapi.io/documentation/developer-docs/latest/concepts/controllers.html#core-controllers)
 * to customize this controller
 */

module.exports = {
  upload: async function(context) {
    // Perform the default upload functionality provided by the strapi upload plugin
    await strapi.plugins.upload.controllers.upload.upload(context);

    let deepai;

    try {
      const form = new FormData();
      const url = context.body[0].formats && context.body[0].formats.small ? context.body[0].formats.small.url : context.body[0].url;

      form.append('image', url);

      // Hit the DeepAI API endpoint. You will not to provide your own apiKey
      const resp = await fetch('https://api.deepai.org/api/nsfw-detector', {
        method: 'POST',
        body: form,
        headers: {
          'api-key': 'blah-blah-blah-blah'
        }
      }); 
      deepai = await resp.json();
    } catch (error) {
      console.error('An error occurred fetching from DeepAI NSFW index', error);
    }

    let imageIsSafe = true;

    if (deepai && deepai.output.nsfw_score > 0.95) {
      // Delete the file if NSFW from the upload.
      await strapi.plugins.upload.services.upload.remove(context.body[0]);
      imageIsSafe = false;
    }

    context.send({
      upload: context.body,
      deepai,
      imageIsSafe
    });
  }
}

That's it! Now when you hit /upload-safe the normal upload strategy will be performed as long as the image is safe.