VirtusLab-Open-Source / strapi-plugin-comments

A plugin for Strapi Headless CMS that provides end to end comments feature with their moderation panel, bad words filtering, abuse reporting and more.
MIT License
413 stars 65 forks source link
api comments customizable rest strapi strapi-admin-panel strapi-plugin
Logo - Strapi Comments plugin

Strapi v4 - Comments plugin

Powerful Strapi based comments moderation tool for you and your users

GitHub package.json version Monthly download on NPM CircleCI codecov.io

UI preview

A plugin for Strapi Headless CMS that provides end to end comments feature with their moderation panel, bad words filtering, abuse reporting and much more.

Table of Contents

  1. ✨ Features
  2. ⏳ Installation
  3. πŸ– Requirements
  4. πŸ”§ Configuration
  5. πŸ•ΈοΈ Public API - REST
  6. πŸ•ΈοΈ Public API - GraphQL
  7. βš—οΈ Custom fields
  8. 🀝 Contributing
  9. πŸ‘¨β€πŸ’» Community support

✨ Features

βš™οΈ Versions

⏳ Installation

Via Strapi Marketplace

As a βœ… verified plugin by Strapi team we're available on the Strapi Marketplace as well as In-App Marketplace where you can follow the installation instructions.

Strapi In-App Marketplace

Via command line

(Use yarn to install this plugin within your Strapi project (recommended). Install yarn with these docs.)

yarn add strapi-plugin-comments@latest

After successful installation you've to re-build your Strapi instance. To archive that simply use:

yarn build
yarn develop

or just run Strapi in the development mode with --watch-admin option:

yarn develop --watch-admin

The Comments plugin should appear in the Plugins section of Strapi sidebar after you run app again.

As a next step you must configure your the plugin by the way you want to. See Configuration section.

All done. Enjoy πŸŽ‰

Working in development mode

  1. Clone repository

    git clone git@github.com:VirtusLab-Open-Source/strapi-plugin-comments.git
  2. Create a soft link in your strapi project to plugin build folder

    ln -s <your path>/strapi-plugin-comments/build <your path>/strapi-project/src/plugins/comments
  3. Run develop or build command

    // Watch for file changes
    yarn develop
    // or run build without nodemon
    yarn build:dev

πŸ– Requirements

Complete installation requirements are exact same as for Strapi itself and can be found in the documentation under Installation Requirements.

Minimum environment requirements

In our minimum support we're following official Node.js releases timelines.

Supported Strapi versions:

This plugin is designed for Strapi v4 and is not working with v3.x. To get version for Strapi v3 install version v1.x.

Plugin dependencies

We recommend always using the latest version of Strapi to start your new projects.

πŸ”§ Configuration

To start your journey with Comments plugin you must first setup it using the dedicated Settings page (v2.0.3 and newer) or for any version, put your configuration in config/plugins.js. Anyway we're recommending the click-through option where your configuration is going to be properly validated.

In v2.0.3 and newer

Version 2.0.3 introduce the intuitive Settings page which you can easly access via Strapi Settings -> Section: Comments Plugin -> Configuration. On dedicated page you will be able to setup all crucial properties which drives the plugin and customize each individual collection for which Comments plugin should be enabled.

Plugin configuration

Note Default configuration for your plugin is fetched from config/plugins.js or directly from the plugin itself. If you would like to customize the default state to which you might revert, please follow the next section.

In v2.0.2 and older + default configuration state for v2.0.3 and newer

To setup amend default plugin configuration we recommend to put following snippet as part of config/plugins.js or config/<env>/plugins.js file. If the file does not exist yet, you have to create it manually. If you've got already configurations for other plugins stores by this way, use just the comments part within exising plugins item.

module.exports = ({ env }) => ({
  //...
  comments: {
    enabled: true,
    config: {
      badWords: false,
      moderatorRoles: ["Authenticated"],
      approvalFlow: ["api::page.page"],
      entryLabel: {
        "*": ["Title", "title", "Name", "name", "Subject", "subject"],
        "api::page.page": ["MyField"],
      },
      blockedAuthorProps: ["name", "email"],
      reportReasons: {
        MY_CUSTOM_REASON: "MY_CUSTOM_REASON",
      },
      gql: {
        // ...
      },
    },
  },
  //...
});

Properties

Additional GQL Configuration

All you need to do is to install and enable @strapi/plugin-graphql for you instance based on the official Strapi v4 docs and decide if you would like to call it by anyone (open for world) or only by authenticated users (Strapi users).

Important! If you're using config/plugins.js to configure your plugins , please put comments property before graphql. Otherwise types are not going to be properly added to GraphQL Schema. That's because of dynamic types which base on plugin configuration which are added on boostrap stage, not register. This is not valid if you're using graphql plugin without any custom configuration, so most of cases in real.

{
  // ...
  "gql": {
    "auth": true // Default: false
  }
  // ...
}

Properties

Queries

See available GQL specification section.

If auth is set to true you must provide relevant authentication headers to all requests like for example:

{
  "Authorization": "Bearer <your token here>"
}

πŸ‘€ RBAC

Plugin provides granular permissions based on Strapi RBAC functionality.

Mandatory permissions

For any role different than Super Admin, to access the Comments panel you must set following permissions:

Optional permissions

Feature / Capability focused permissions:

Base Comment model

{
  "id": 1,
  "content": "My comment content",
  "blocked": null,
  "blockedThread": true,
  "blockReason": null,
  "authorUser": null,
  "removed": null,
  "approvalStatus": "APPROVED", // Only in case of enabled approval flow. Default: null
  "author": {
    "id": "207ccfdc-94ba-45eb-979c-790f6f49c392", // For Strapi users id reflects to the format used by your Strapi
    "name": "Joe Doe", // For Strapi users it is equal to 'username' field
    "email": "jdoe@sample.com",
    "avatar": null
  },
  "createdAt": "2020-07-14T20:13:01.649Z",
  "updatedAt": "2020-07-14T20:13:01.670Z",
  "related": {}, // Related content type entity
  "reports": [] // Reports issued against this comment
}

πŸ•ΈοΈ Public REST API specification

Strapi Users vs. Generic authors

Keep in mind that if you're using auth / authz your requests to setup proper user contexts it has got higher priority in order to take author data comparing to author property provided as part of your payload.

Get Comments

GraphQL equivalent: Public GraphQL API -> Get Comments

GET <host>/api/comments/api::<collection name>.<content type name>:<entity id>

Return a hierarchical tree structure of comments for specified instance of Content Type like for example Page with ID: 1.

Example URL: https://localhost:1337/api/comments/api::page.page:1

Example response body

[
  {
    // -- Comment Model fields ---,
    "children": [
      {
        // -- Comment Model fields ---,
        "children": [
          // ...
        ]
      }
      // ...
    ]
  }
  // ...
]

Strapi REST API properties support:

Get Comments (flat structure)

GraphQL equivalent: Public GraphQL API -> Get Comments (flat structure)

GET <host>/api/comments/api::<collection name>.<content type name>:<entity id>/flat

Return a flat structure of comments for specified instance of Content Type like for example Page with ID: 1

Example URL: https://localhost:1337/api/comments/api::page.page:1/flat

Example response body

{
  "data": [
    {
      // -- Comment Model fields ---
    },
    {
      // -- Comment Model fields ---
    }
    // ...
  ],
  "meta": {
    "pagination": {
      // payload based on Strapi REST Pagination specification
    }
  }
}

Possible response codes

Strapi REST API properties support:

Get Comments (by Author)

GraphQL equivalent: Public GraphQL API -> Get Comments (by Author)

GET <host>/api/comments/author/<id>/<?type>

Return a flat structure of comments by specified Author for example Author with ID: 1

Example URL: https://localhost:1337/api/comments/author/1 - get comments by ID:1 of Strapi User Example URL: https://localhost:1337/api/comments/author/1/generic - get comments by ID:1 of Generic User

Skipping fields

To skip a field from the response you can use a query param called omit. It is a list of Comment entity fields you want to ignore. For example ?omit[]=author&omit[]=related.

Remember! id field is required so it will not be skipped.

Example response body

{
  "data": [
    {
      // -- Comment Model fields ---
    },
    {
      // -- Comment Model fields ---
    }
    // ...
  ],
  "meta": {
    "pagination": {
      // payload based on Strapi REST Pagination specification
    }
  }
}

Possible response codes

Strapi REST API properties support:

Post a Comment

GraphQL equivalent: Public GraphQL API -> Post a Comments

POST <host>/api/comments/api::<collection name>.<content type name>:<entity id>

Posts a Comment related to specified instance of Content Type like for example Page with ID: 1

Example URL: https://localhost:1337/api/comments/api::page.page:1

Example request body

Generic (non Strapi User)

{
  "author": {
    "id": "<any ID like value>",
    "name": "Joe Doe",
    "email": "jdoe@sample.com",
    "avatar": "<any image url>"
  },
  "content": "My sample response",
  "threadOf": 2 // id of comment we would like to start / continue the thread (Optional)
}

Strapi user

Author is taken directly from the request context

{
  "content": "My sample response",
  "threadOf": 2 // id of comment we would like to start / continue the thread (Optional)
}

Example response body

{
  // -- Comment Model fields ---
}

Possible response codes

Update Comment

GraphQL equivalent: Public GraphQL API -> Update Comments

PUT <host>/api/comments/api::<collection name>.<content type name>:<entity id>/comment/<commentId>

Updates a specified Comment content based on it commentId and related to specified instance of Content Type like for example Page with ID: 1

Example URL: https://localhost:1337/api/comments/api::page.page:1/comment/2

Example request body

Generic (non Strapi User)

{
  "author": {
    "id": "<any ID like value>"
  },
  "content": "My sample response"
}

Strapi user

Author is taken directly from the request context

{
  "content": "My sample response"
}

Example response body

{
  // -- Comment Model fields ---
}

Possible response codes

Delete Comment

GraphQL equivalent: Public GraphQL API -> Delete Comment

DELETE <host>/api/comments/api::<collection name>.<content type name>:<entity id>/comment/<commentId>?authorId=<authorId>

Deletes a specified Comment based on it commentId and related to specified instance of Content Type like for example Page with ID: 1.

Example URL: https://localhost:1337/api/comments/api::page.page:1/comment/1?authorId=1

Example response body

{
  // -- Empty Response ---
}

Possible response codes

Issue Abuse Report against specified Comment

GraphQL equivalent: Public GraphQL API -> Issue Abuse Report against specified Comment

POST <host>/api/comments/api::<collection name>.<content type name>:<entity id>/comment/<commentId>/report-abuse

Reports abuse in specified Comment content based on it commentId and related to specified instance of Content Type like for example Page with ID: 1 and requests moderator attention.

Example URL: https://localhost:1337/api/comments/api::page.page:1/comment/2/report-abuse

Example request body

{
  "reason": "<reason enum>",
  "content": "This comment is not relevant"
}

Available reason enums: BAD_WORDS, OTHER, DISCRIMINATION (want more? See configuration section.)

Example response body

{
  // -- Comment Abuse Report fields ---
}

Possible response codes

πŸ•ΈοΈ Public GraphQL specification

Strapi Users vs. Generic authors

Keep in mind that if you're using auth / authz your requests to setup proper user contexts it has got higher priority in order to take author data comparing to author property provided as part of your payload.

Testing

To test all queries and understand the schemas use GraphQL Playground exposed by @strapi/plugin-graphql on http://localhost:1337/graphql

Get Comments

REST API equivalent: Public REST API -> Get Comments

Example request

query {
  findAllInHierarchy(relation: "api::page.page:1") {
    id
    content
    blocked
    children {
      id
      content
    }
    threadOf {
      id
    }
    author {
      id
      name
    }
  }
}

Example response

{
  "data": {
    "findAllInHierarchy": [
      {
        "id": 1,
        "content": "Test",
        "blocked": false,
        "children": [
          {
            "id": 6,
            "content": "Text to search for"
          }
          // ...
        ],
        "threadOf": null,
        "author": {
          "id": "123456",
          "name": "Joe Doe"
        }
      }
      // ...
    ]
  }
}

Strapi GraphQL API properties support:

Get Comments (flat structure)

REST API equivalent: Public REST API -> Get Comments (flat structure)

Example request

query {
  findAllFlat(
    relation: "api::page.page:1"
    filters: { content: { contains: "Test" } }
  ) {
    id
    content
    blocked
    threadOf {
      id
    }
    author {
      id
      name
    }
  }
}

Example response

{
  "data": {
    "findAllFlat": [
      {
        "id": 3,
        "content": "Test",
        "blocked": false,
        "threadOf": null,
        "author": {
          "id": "123456",
          "name": "Joe Doe"
        }
      },
      // ...
    ]
  }

Strapi GraphQL API properties support:

Get Comments (by Author)

REST API equivalent: Public REST API -> Get Comments (by Author)

Example request

query {
  findAllPerAuthor(authorId: 1, authorType: STRAPI) { // authorType might be one of [GENERIC, STRAPI]
    data {
      id
      content
      blocked
      threadOf {
        id
      }
    }
  }
}

Example response

{
  "data": {
    "findAllPerAuthor": {
      "data": [
        {
          "id": 4,
          "content": "Hackaton test comment",
          "blocked": false,
          "threadOf": {
            "id": 1
          }
        }
      // ...
      ]
    }
  }

Strapi GraphQL API properties support:

Post a Comment

REST API equivalent: Public REST API -> Post a Comment

Example request

mutation createComment {
  createComment(
    input: {
      relation: "api::page.page:1"
      content: "Hello World!"
      threadOf: 3
      author: { id: "12345678", name: "John Wick", email: "test@test.pl" } # Optional if using auth / authz requests
    }
  ) {
    id
    content
    threadOf {
      id
    }
    author {
      id
      name
    }
  }
}

Example response

{
  "data": {
    "createComment": {
      "id": 34,
      "content": "Hello World!",
      "threadOf": {
        "id": 3
      },
      "author": {
        "id": "12345678",
        "name": "John Wick"
      }
    }
  }
}

Update Comment

REST API equivalent: Public REST API -> Update Comment

Example request

mutation updateComment {
  updateComment(
    input: {
      id: 34
      relation: "api::page.page:1"
      content: "I've changed it!"
      author: { id: "12345678" } # Optional if using auth / authz requests
    }
  ) {
    id
    content
    threadOf {
      id
    }
    author {
      id
      name
    }
    createdAt
    updatedAt
  }
}

Example response

{
  "data": {
    "updateComment": {
      "id": 34,
      "content": "I've changed it!",
      "threadOf": {
        "id": 3
      },
      "author": {
        "id": "12345678",
        "name": "John Wick"
      },
      "createdAt": "2022-01-26T07:45:35.978Z",
      "updatedAt": "2022-01-26T07:47:44.659Z"
    }
  }
}

Delete Comment

REST API equivalent: Public REST API -> Delete Comment

Example request

mutation removeComment {
  removeComment(
    input: {
      id: 33
      relation: "api::page.page:1"
      author: { id: "12345678" } # Optional if using auth / authz requests
    }
  ) {
    id
    removed
  }
}

Example response

{
  "data": {
    "removeComment": {
      "id": 33,
      "removed": true
    }
  }
}

Issue Abuse Report against specified Comment

REST API equivalent: Public REST API -> Issue Abuse Report against specified Comment

Example request body

mutation createAbuseReport {
  createAbuseReport(
    input: {
      commentId: 34
      relation: "api::page.page:1"
      reason: BAD_LANGUAGE
      content: "Rude language"
    }
  ) {
    id
    reason
    content
    related {
      id
      author {
        id
        name
      }
    }
  }
}

Available reason enums: BAD_WORDS, OTHER, DISCRIMINATION (want more? See configuration section.)

Example response

{
  "data": {
    "createAbuseReport": {
      "id": 28,
      "content": "Rude language",
      "reason": "DISCRIMINATION",
      "related": {
        "id": 34,
        "author": {
          "id": "12345678",
          "name": "John Wick"
        }
      }
    }
  }
}

🧩 Examples

Live example of plugin usage can be found in the VirtusLab Strapi Examples repository.

βš—οΈ Custom fields

For developers who upgrades their Strapi instance custom field from Comments plugin is available. Custom field can be picked from content types' edit page or added in definition file.

Read more about this feature in Strapi's docs.

Model lifecycle hooks

Comments plugin allows to register lifecycle hooks for Comment and Comment report content types.

You can read more about lifecycle hooks here. (You can set a listener for all of the hooks).

Lifecycle hooks can be register either in register() or bootstrap() methods of your server. You can register more than one listener for a specified lifecycle hook. For example: you want to do three things on report creation and do not want to handle all of these actions in one big function. You can split logic in as many listeners as you want.

Listeners can by sync and async.

Be aware that lifecycle hooks registered in register() may be fired by plugin's bootstrapping. If you want listen to events triggered after server's startup use bootstrap().

Example:

  const commentsCommonService = strapi
    .plugin("comments")
    .service("common");

  commentsCommonService.registerLifecycleHook({
    callback: async ({ action, result }) => {
      const saveResult = await logIntoSystem(action, result);

      console.log(saveResult);
    },
    contentTypeName: "comment",
    hookName: "afterCreate",
  });

  commentsCommonService.registerLifecycleHook({
    callback: async ({ action, result }) => {
      const saveResult = await logIntoSystem(action, result);

      console.log(saveResult);
    },
    contentTypeName: "report",
    hookName: "afterCreate",
  });

🀝 Contributing

Feel free to fork and make a Pull Request to this plugin project. All the input is warmly welcome!

πŸ‘¨β€πŸ’» Community support

For general help using Strapi, please refer to the official Strapi documentation. For additional help, you can use one of these channels to ask a question:

πŸ“ License

MIT License Copyright (c) VirtusLab Sp. z o.o. & Strapi Solutions.