Security News
Supply Chain Attack Detected in Solana's web3.js Library
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
strapi-plugin-reactions
Advanced tools
A plugin for Strapi Headless CMS that provides flexible & configurable reactions experience to any Content Types.
Request a feature by raising an issue.
Because of Emoji usage, you will need (or maybe you already did it because of earlier requirements) to extend the database connection section in your Strapi project configuration.
// config/database.ts
// config/<env>/database.ts
connection: {
charset: 'utf8mb4',
collation: 'utf8mb4_unicode_ci',
// your database credentials
}
Based on this specific charset, your emoji reactions are going to be saved in the database like a charm. See the reference issue raised on Strapi repository.
(Use yarn to install this plugin within your Strapi project (recommended). Install yarn with these docs.)
yarn add strapi-plugin-reactions@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 Reactions plugin should appear in the Settings section of Strapi after you run app again.
As a next step you must configure your the plugin by adding types of reactions you want to use. See Configuration section.
All done. Enjoy π
Complete installation requirements are exact same as for Strapi itself and can be found in the documentation under Installation Requirements.
Minimum environment requirements
>=18.0.0 <=20.x.x
>=6.x.x
In our minimum support we're following official Node.js releases timelines.
Supported Strapi versions:
Plugin dependencies
@strapi/plugin-graphql
- required to run because built-in support for GraphQL handled by this pluginWe recommend always using the latest version of Strapi to start your new projects.
To start your journey with Reactions plugin you must first setup types of reactions using the dedicated Settings page.
There is no need to provide any specific changed in the plugin configuration files extept enabling it.
export default () => ({
//...
reactions: {
enabled: true,
config: {
gql: {...},
},
},
//...
});
gql
- If you're using GraphQL that's the right place to put all necessary settings. More here Using reactions with GraphQL requires both plugins to be installed and working. You can find installation guide for GraphQL plugin here. To properly configure GQL to work with reactions you should provide gql
prop. This should contain union types that will be used to define GQL response format for your data while fetching:
Important! If you're using
config/plugins.{js,ts}
to configure your plugins , please putreactions
property beforegraphql
. 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 onbootstrap
stage, notregister
. This is not valid if you're usinggraphql
plugin without any custom configuration, so most of cases in real.
related: ReactionRelated
This prop should look as follows:
gql: {
reactionRelated: ['<your GQL related content types>'],
},
for example:
gql: {
reactionRelated: ['Page', 'BlogPost'],
},
where Page
and BlogPost
are your type names for the Content Types you're using reactions with.
Plugin provides granular permissions based on Strapi RBAC functionality.
For any role different than Super Admin, to access the Reactions settings you must set following permissions:
{
"documentId": "njx99iv4p4txuqp307ye8625",
"name": "Like",
"slug": "like",
"emoji": "π",
"emojiFallbackUrl": "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f44d.png",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"icon": null
}
{
"documentId": "njx99iv4p4txuqp307ye8625",
"kind": { // Type of reaction, not provided when listing by exact kind
"documentId": "njx99iv4p4txuqp307ye8625",
"slug": "like",
"name": "Like"
},
"user": { // User who trigger reaction, not provided when listing by exact user
"documentId": "njx99iv4p4txuqp307ye8625",
"username": "Joe Doe",
"email": "jdoe@sample.com",
},
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
}
GraphQL equivalent: Public GraphQL API -> Get reaction kinds / types
GET <host>/api/reactions/kinds
Return a list of available reaction kinds to use on the end user interface and expose for interaction with users.
Example URL: https://localhost:1337/api/reactions/kinds
Example response body
[
{
"documentId": "njx99iv4p4txuqp307ye8625",
"name": "Like",
"slug": "like",
"emoji": "π",
"emojiFallbackUrl": "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f44d.png",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"icon": null
},
// ...
]
GraphQL equivalent: Public GraphQL API -> List all reactions associated with Content Type
GET <host>/api/reactions/list/single/<single type UID>?locale=<locale code>
GET <host>/api/reactions/list/collection/<collection type UID>/<documentId>?locale=<locale code>
Return all reactions associated with provided Collection / Single Type UID and Content Type Document ID with following combinations:
Authorization
headerAuthorization
headerExample URL: https://localhost:1337/api/reactions/list/single/api::homepage.homepage?locale=en
Example URL: https://localhost:1337/api/reactions/list/collection/api::post.post/njx99iv4p4txuqp307ye8625?locale=en
Example response body
[
{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"kind":{
"documentId": "njx99iv4p4txuqp307ye8625",
"slug": "like",
"name": "Like"
},
"user":{ // Added if no user context provided to identify who made such reaction
"documentId": "njx99iv4p4txuqp307ye8625",
"username": "mziarko+1@virtuslab.com",
"email": "mziarko+1@virtuslab.com"
}
},
// ...
]
GraphQL equivalent: Public GraphQL API -> List all reactions associated with particular user
GET <host>/api/reactions/list/user
GET <host>/api/reactions/list/user/<user id>
Return all reactions associated with provided user:
Authorization
headerAuthorization
headerExample URL: https://localhost:1337/api/reactions/list/user
Example URL: https://localhost:1337/api/reactions/list/user/1
Example response body
[
{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"kind":{
"slug": "like",
"name": "Like"
},
"related":{
"documentId": "njx99iv4p4txuqp307ye8625",
"id": 1,
"locale": "en",
//...
}
},
// ...
]
GraphQL equivalent: Public GraphQL API -> List all reactions of kind / type associated with Content Type
GET <host>/api/reactions/list/<type slug>/single/<single type UID>?locale=<locale code>
GET <host>/api/reactions/list/<type slug>/collection/<collection type UID>/<documentId>?locale=<locale code>
Return all reactions of specific kind / type associated with provided Collection / Single Type UID and Content Type Document ID with following combinations:
Authorization
headerAuthorization
headerExample URL: https://localhost:1337/api/reactions/list/like/single/api::homepage.homepage?locale=en
Example URL: https://localhost:1337/api/reactions/list/like/collection/api::post.post/njx99iv4p4txuqp307ye8625?locale=en
Example response body
[
{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"user":{ // Added if no user context provided to identify who made such reaction
"documentId": "njx99iv4p4txuqp307ye8625",
"username": "mziarko+1@virtuslab.com",
"email": "mziarko+1@virtuslab.com"
}
},
// ...
]
GraphQL equivalent: Public GraphQL API -> List all reactions of kind associated with particular user
GET <host>/api/reactions/list/<type slug>/user
GET <host>/api/reactions/list/<type slug>/user/<user id>
Return all reactions of specific kind associated with provided user:
Authorization
headerAuthorization
headerExample URL: https://localhost:1337/api/reactions/list/like/user
Example URL: https://localhost:1337/api/reactions/list/like/user/1
Example response body
[
{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"kind":{
"slug": "like",
"name": "Like"
},
"related":{
"documentId": "njx99iv4p4txuqp307ye8625",
"id": 1,
"locale": "en",
//...
}
},
// ...
]
GraphQL equivalent: Public GraphQL API -> Set reaction for Content Type
POST <host>/api/reactions/set/<type slug>/single/<single type UID>?locale=<locale code>
POST <host>/api/reactions/set/<type slug>/collection/<collection type UID>/<documentId>?locale=<locale code>
Create reaction of specific kind / type associated with provided Collection / Single Type UID and Content Type Document ID.
Authorization
header is required
Example URL: https://localhost:1337/api/reactions/set/like/single/api::homepage.homepage?locale=en
Example URL: https://localhost:1337/api/reactions/set/like/collection/api::post.post/njx99iv4p4txuqp307ye8625?locale=en
Example response body
{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"relatedUid": "api::post.post:njx99iv4p4txuqp307ye8625"
}
GraphQL equivalent: Public GraphQL API -> Unset reaction for Content Type
DELETE <host>/api/reactions/unset/<type slug>/single/<single type UID>?locale=<locale code>
DELETE <host>/api/reactions/unset/<type slug>/collection/<collection type UID>/<documentId>?locale=<locale code>
Delete reaction of specific kind / type associated with provided Collection / Single Type UID and Content Type Document ID.
Authorization
header is required
Example URL: https://localhost:1337/api/reactions/unset/like/single/api::homepage.homepage?locale=en
Example URL: https://localhost:1337/api/reactions/unset/like/collection/api::post.post/njx99iv4p4txuqp307ye8625?locale=en
Example response body
true
GraphQL equivalent: Public GraphQL API -> Toggle reaction for Content Type
POST <host>/api/reactions/toggle/<type slug>/single/<single type UID>?locale=<locale code>
POST <host>/api/reactions/toggle/<type slug>/collection/<collection type UID>/<documentId>?locale=<locale code>
Toggle reaction of specific kind / type associated with provided Collection / Single Type UID and Content Type Document ID.
Authorization
header is required
Example URL: https://localhost:1337/api/reactions/toggle/like/single/api::homepage.homepage?locale=en
Example URL: https://localhost:1337/api/reactions/toggle/like/collection/api::post.post/njx99iv4p4txuqp307ye8625?locale=en
Example response body
{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"relatedUid": "api::post.post:njx99iv4p4txuqp307ye8625"
}
// or
true
toggle
reaction is settoogle
no reaction is settoggle
just specified reaction stays, rest becomes unsetTesting
To test all queries and understand the schemas use GraphQL Playground exposed by
@strapi/plugin-graphql
onhttp://localhost:1337/graphql
REST API equivalent: Public REST API -> Get reaction kinds / types
Return a list of available reaction kinds to use on the end user interface and expose for interaction with users.
Example request
query {
reactionKinds {
slug
name
emoji
icon {
url
}
}
}
Example response body
{
"data": {
"reactionKinds": [
{
"slug": "like",
"name": "Like",
"emoji": "π",
"icon": null
}
]
}
}
REST API equivalent: Public REST API -> List all reactions associated with Content Type
Return all reactions associated with provided Collection / Single Type UID and Content Type Document ID with following combinations:
reactionsList
- no Authorization
header provided (open for public)reactionsListPerUser
- an Authorization
header is mandatoryExample request
query {
reactionsList(uid: "api::post.post", documentId: "njx99iv4p4txuqp307ye8625", locale: "en") {
documentId
kind {
slug
name
emoji
}
user {
email
}
}
}
query {
reactionsListPerUser(uid: "api::post.post", documentId: "njx99iv4p4txuqp307ye8625", locale: "en") {
documentId
kind {
slug
name
emoji
}
}
}
Example response body
{
"data": {
"reactionsList": [
{
"documentId": "njx99iv4p4txuqp307ye8625",
"kind": {
"slug": "like",
"name": "Like",
"emoji": "π"
},
"user": {
"email": "mziarko+1@virtuslab.com"
},
"createdAt": "2023-09-14T20:13:01.670Z"
}
]
}
}
// --------------------------------
{
"data": {
"reactionsListPerUser": [
{
"documentId": "njx99iv4p4txuqp307ye8625",
"kind": {
"slug": "like",
"name": "Like",
"emoji": "π"
},
"createdAt": "2023-09-14T20:13:01.670Z"
}
]
}
}
REST API equivalent: Public REST API -> List all reactions associated with particular user
Return all reactions associated with provided user:
reactionsListAllPerUser
- an Authorization
header is mandatory or userId
in argsExample request
query {
reactionsListAllPerUser {
documentId
createdAt
kind {
slug
}
related {
documentId
id
locale
}
}
}
query {
reactionsListAllPerUser(userId: 1) {
documentId
createdAt
kind {
slug
}
related {
documentId
id
locale
}
}
}
Example response body
{
"data": {
"reactionsListAllPerUser": [
{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.670Z",
"kind": {
"slug": "like"
},
"related": {
"documentId": "njx99iv4p4txuqp307ye8625",
"id": 1,
"locale": "en"
}
}
]
}
}
REST API equivalent: Public REST API -> List all reactions of kind / type associated with Content Type
Return all reactions of specific kind / type associated with provided Collection / Single Type UID and Content Type Document ID with following combinations:
reactionsList
- no Authorization
header provided (open for public)reactionsListPerUser
- an Authorization
header is mandatoryExample request
query {
reactionsList(kind: "like", uid: "api::post.post", documentId: "njx99iv4p4txuqp307ye8625", locale: "en") {
documentId
user {
email
}
createdAt
}
}
query {
reactionsListPerUser(kind: "like", uid: "api::post.post", documentId: "njx99iv4p4txuqp307ye8625", locale: "en") {
documentId
createdAt
}
}
Example response body
{
"data": {
"reactionsList": [
{
"documentId": "njx99iv4p4txuqp307ye8625",
"user": {
"email": "mziarko+1@virtuslab.com"
},
"createdAt": "2023-09-14T20:13:01.670Z"
}
]
}
}
// --------------------------------
{
"data": {
"reactionsListPerUser": [
{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.670Z"
}
]
}
}
REST API equivalent: Public REST API -> List all reactions of kind associated with particular user
Return all reactions of specific kind associated with provided user:
reactionsListAllPerUser
- an Authorization
header is mandatory or userId
in argsExample request
query {
reactionsListAllPerUser(kind: "like") {
documentId
createdAt
related {
documentId
id
locale
}
}
}
query {
reactionsListAllPerUser(kind: "like", userId: 1) {
documentId
createdAt
related {
documentId
id
locale
}
}
}
Example response body
{
"data": {
"reactionsListAllPerUser": [
{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.670Z",
"related": {
"documentId": "njx99iv4p4txuqp307ye8625",
"id": 1,
"locale": "en"
}
}
]
}
}
REST API equivalent: Public REST API -> Set reaction for Content Type
Create reaction of specific kind / type associated with provided Collection / Single Type UID and Content Type Document ID.
Authorization
header is required
Example request
mutation reactionSet {
reactionSet(
input: {
kind: "like",
uid: "api::post.post",
documentId: "njx99iv4p4txuqp307ye8625",
locale: "en"
}
) {
documentId
}
}
Example response body
{
"data": {
"reactionSet": {
"documentId": "njx99iv4p4txuqp307ye8625"
}
}
}
REST API equivalent: Public REST API -> Unset reaction for Content Type
Delete reaction of specific kind / type associated with provided Collection / Single Type UID and Content Type Document ID.
Authorization
header is required
Example request
mutation reactionUnset {
reactionUnset(
input: {
kind: "like",
uid: "api::post.post",
documentId: "njx99iv4p4txuqp307ye8625",
locale: "en"
}
) {
documentId
}
}
Example response body
{
"data": {
"reactionUnset": {
"documentId": null
}
}
}
REST API equivalent: Public REST API -> Toggle reaction for Content Type
Toggle reaction of specific kind / type associated with provided Collection / Single Type UID and Content Type Document ID.
Authorization
header is required
Example request
mutation reactionToggle {
reactionToggle(
input: {
kind: "like",
uid: "api::post.post",
documentId: "njx99iv4p4txuqp307ye8625",
locale: "en"
}
) {
documentId
}
}
Example response body
{
"data": {
"reactionToggle": {
"documentId": "njx99iv4p4txuqp307ye8625"
}
}
}
// or
{
"data": {
"reactionToggle": {
"documentId": null
}
}
}
toggle
reaction is settoogle
no reaction is settoggle
just specified reaction stays, rest becomes unsetYou can use this service method for example in your Strapi Content API findOne
method to enrich the metadata of retrieved entity by associated reactions.
What is important, service method does not modify default data
schema of Strapi Content API. All additions are made in the meta
property in the form of:
// ...
"meta": {
// ...
"reactions": {
"<type slug>": [
// list of reactions
],
// ...
}
}
// src/api/post/controllers/post.ts
'use strict';
/**
* post controller
*/
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::post.post', ({ strapi }) => ({
async findOne(ctx) {
const response = await super.findOne(ctx);
return strapi
.service('plugin::reactions.enrich')
.enrichOne('api::post.post', response);
},
}));
{
"data":{
"documentId": "njx99iv4p4txuqp307ye8625",
"attributes":{
// Content type attributes
}
},
"meta":{
"reactions":{
"like":[{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"relatedUid": "api::post.post:2",
"kind":{
"documentId": "njx99iv4p4txuqp307ye8625",
"name": "Like",
"slug": "like",
"emoji": "π",
"emojiFallbackUrl": "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f44d.png",
"icon": null
},
"user":{
"documentId": "njx99iv4p4txuqp307ye8625",
"username": "mziarko+1@virtuslab.com",
"email": "mziarko+1@virtuslab.com"
}
},
// ...
],
// ...
}
}
}
You can use this service method for example in your Strapi Content API find
method to enrich the metadata of retrieved entities set by associated reactions.
What is important, service method does not modify default data
schema of Strapi Content API. All additions are made in the meta
property in the form of:
// ...
"meta": {
// ...
"reactions": {
"<content type id>": {
"<type slug>": [
// list of reactions
],
// ...
}
}
}
// src/api/post/controllers/post.ts
'use strict';
/**
* post controller
*/
import { factories } from '@strapi/strapi'
export default factories.createCoreController('api::post.post', ({ strapi }) => ({
async find(ctx) {
const response = await super.find(ctx);
return strapi
.service('plugin::reactions.enrich')
.enrichMany('api::post.post', response);
},
}));
{
"data": [{
"documentId": "njx99iv4p4txuqp307ye8625",
"attributes":{
// Content type attributes
}
}, {
"documentId": 2,
"attributes":{
// Content type attributes
}
}
],
"meta":{
"reactions":{
"njx99iv4p4txuqp307ye8625": {
"like":[{
"documentId": "njx99iv4p4txuqp307ye8625",
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"relatedUid": "api::post.post:2",
"kind":{
"documentId": "njx99iv4p4txuqp307ye8625",
"name": "Like",
"slug": "like",
"emoji": "π",
"emojiFallbackUrl": "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f44d.png",
"icon": null
},
"user":{
"documentId": "njx99iv4p4txuqp307ye8625",
"username": "mziarko+1@virtuslab.com",
"email": "mziarko+1@virtuslab.com"
}
},
// ...
],
// ...
},
"2": {}
}
}
}
Q: I'm exporting / moving data between environments. Will I lost all reactions?
A: No you won't lost anything. Reactions plugin is based on real relations (Polymorphic relations) and all the data should be exported / moved and re-indexed properly on your new environment.
The only thing you might need to do after import is described below β¬οΈ
Q: I've imported data to the new instance and I'm not seeing reaction linked to my Content Types or they are wrong. What to do?
A: Most probably after the import unique identifiers of your Content Types has changed according to the new environment.
You might need to use one of special admin actions for the Reactions plugin like "Synchronize associations". To get access to it, you must be Super Admin or have assigned Settings: Admin permission to any of your roles.
This is not destructive action and it just goes through all existing Reactions and update their search key according to linked Content Type UID and ID like api::post.post:njx99iv4p4txuqp307ye8625
.
Feel free to fork and make a Pull Request to this plugin project. All the input is warmly welcome!
Clone repository
git clone git@github.com:VirtusLab-Open-Source/strapi-plugin-reactions.git
Run install
& watch:link
command
// Install all dependencies
yarn install
// Watch for file changes using `plugin-sdk` and follow the instructions provided by this official Strapi developer tool
yarn watch:link
Within the Strapi project, modify config/plugins.{js,ts}
for imgix
//...
'reactions': {
enabled: true,
//...
}
//...
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:
[VirtusLab]
prefix and DM.MIT License Copyright (c) VirtusLab Sp. z o.o. & Strapi Solutions.
FAQs
Strapi - Reactions plugin
The npm package strapi-plugin-reactions receives a total of 75 weekly downloads. As such, strapi-plugin-reactions popularity was classified as not popular.
We found that strapi-plugin-reactions demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.