Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
strapi-plugin-reactions
Advanced tools
A plugin for Strapi Headless CMS that provides flexible & configurable reactions experience to any Content Types.
Before releasing the RC and first Public Release we would like to introduce:
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.
module.exports = ({ env }) => ({
//...
reactions: {
enabled: true,
},
//...
});
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:
{
"id": 1,
"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
}
{
"id": 1,
"kind": { // Type of reaction, not provided when listing by exact kind
"id": 1,
"slug": "like",
"name": "Like"
},
"user": { // User who trigger reaction, not provided when listing by exact user
"id": 1,
"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
[
{
"id": 1,
"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>
GET <host>/api/reactions/list/collection/<collection type UID>/<id>
Return all reactions assiciated with provided Collection / Single Type UID and Content Type ID with following combinations:
Authorization
headerAuthorization
headerExample URL: https://localhost:1337/api/reactions/list/single/api::homepage.homepage
Example URL: https://localhost:1337/api/reactions/list/collection/api::blog-post.blog-post/1
Example response body
[
{
"id": 1,
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"kind":{
"id": 1,
"slug": "like",
"name": "Like"
},
"user":{ // Added if no user context provided to identify who made such reaction
"id": 1,
"username": "mziarko+1@virtuslab.com",
"email": "mziarko+1@virtuslab.com"
}
},
// ...
]
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>
GET <host>/api/reactions/list/<type slug>/collection/<collection type UID>/<id>
Return all reactions of specific kind / type assiciated with provided Collection / Single Type UID and Content Type ID with following combinations:
Authorization
headerAuthorization
headerExample URL: https://localhost:1337/api/reactions/list/like/single/api::homepage.homepage
Example URL: https://localhost:1337/api/reactions/list/like/collection/api::blog-post.blog-post/1
Example response body
[
{
"id": 1,
"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
"id": 1,
"username": "mziarko+1@virtuslab.com",
"email": "mziarko+1@virtuslab.com"
}
},
// ...
]
GraphQL equivalent: Public GraphQL API -> Set reaction for Content Type
POST <host>/api/reactions/set/<type slug>/single/<single type UID>
POST <host>/api/reactions/set/<type slug>/collection/<collection type UID>/<id>
Create reaction of specific kind / type assiciated with provided Collection / Single Type UID and Content Type ID.
Authorization
header is required
Example URL: https://localhost:1337/api/reactions/set/like/single/api::homepage.homepage
Example URL: https://localhost:1337/api/reactions/set/like/collection/api::blog-post.blog-post/1
Example response body
{
"id": 1,
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"relatedUid": "api::blog-post.blog-post:1"
}
GraphQL equivalent: Public GraphQL API -> Unset reaction for Content Type
DELETE <host>/api/reactions/unset/<type slug>/single/<single type UID>
DELETE <host>/api/reactions/unset/<type slug>/collection/<collection type UID>/<id>
Delete reaction of specific kind / type assiciated with provided Collection / Single Type UID and Content Type ID.
Authorization
header is required
Example URL: https://localhost:1337/api/reactions/unset/like/single/api::homepage.homepage
Example URL: https://localhost:1337/api/reactions/unset/like/collection/api::blog-post.blog-post/1
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>
POST <host>/api/reactions/toggle/<type slug>/collection/<collection type UID>/<id>
Toggle reaction of specific kind / type assiciated with provided Collection / Single Type UID and Content Type ID.
Authorization
header is required
Example URL: https://localhost:1337/api/reactions/toggle/like/single/api::homepage.homepage
Example URL: https://localhost:1337/api/reactions/toggle/like/collection/api::blog-post.blog-post/1
Example response body
{
"id": 1,
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"relatedUid": "api::blog-post.blog-post:1"
}
// 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 assiciated with provided Collection / Single Type UID and Content Type ID with following combinations:
reactionsListAll
- no Authorization
header provided (open for public)reactionsListPerUser
- an Authorization
header is mandatoryExample request
query {
reactionsListAll(uid: "api::blog-post.blog-post", id: 1) {
id
kind {
slug
name
emoji
}
user {
email
}
}
}
query {
reactionsListPerUser(uid: "api::blog-post.blog-post", id: 1) {
id
kind {
slug
name
emoji
}
}
}
Example response body
{
"data": {
"reactionsListAll": [
{
"id": 1,
"kind": {
"slug": "like",
"name": "Like",
"emoji": "👍"
},
"user": {
"email": "mziarko+1@virtuslab.com"
},
"createdAt": "2023-09-14T20:13:01.670Z"
}
]
}
}
// --------------------------------
{
"data": {
"reactionsListPerUser": [
{
"id": 1,
"kind": {
"slug": "like",
"name": "Like",
"emoji": "👍"
},
"createdAt": "2023-09-14T20:13:01.670Z"
}
]
}
}
REST API equivalent: Public REST API -> List all reactions of kind / type associated with Content Type
Return all reactions of specific kind / type assiciated with provided Collection / Single Type UID and Content Type ID with following combinations:
reactionsListAll
- no Authorization
header provided (open for public)reactionsListPerUser
- an Authorization
header is mandatoryExample request
query {
reactionsListAll(kind: "like", uid: "api::blog-post.blog-post", id: 1) {
id
user {
email
}
createdAt
}
}
query {
reactionsListPerUser(kind: "like", uid: "api::blog-post.blog-post", id: 1) {
id
createdAt
}
}
Example response body
{
"data": {
"reactionsListAll": [
{
"id": 1,
"user": {
"email": "mziarko+1@virtuslab.com"
},
"createdAt": "2023-09-14T20:13:01.670Z"
}
]
}
}
// --------------------------------
{
"data": {
"reactionsListPerUser": [
{
"id": 1,
"createdAt": "2023-09-14T20:13:01.670Z"
}
]
}
}
REST API equivalent: Public REST API -> Set reaction for Content Type
Create reaction of specific kind / type assiciated with provided Collection / Single Type UID and Content Type ID.
Authorization
header is required
Example request
mutation reactionSet {
reactionSet(
input: {
kind: "like",
uid: "api::blog-post.blog-post"
id: 1
}
) {
id
}
}
Example response body
{
"data": {
"reactionSet": {
"id": 1
}
}
}
REST API equivalent: Public REST API -> Unset reaction for Content Type
Delete reaction of specific kind / type assiciated with provided Collection / Single Type UID and Content Type ID.
Authorization
header is required
Example request
mutation reactionUnset {
reactionUnset(
input: {
kind: "like",
uid: "api::blog-post.blog-post"
id: 1
}
) {
id
}
}
Example response body
{
"data": {
"reactionUnset": {
"id": null
}
}
}
REST API equivalent: Public REST API -> Toggle reaction for Content Type
Toggle reaction of specific kind / type assiciated with provided Collection / Single Type UID and Content Type ID.
Authorization
header is required
Example request
mutation reactionToggle {
reactionToggle(
input: {
kind: "like",
uid: "api::blog-post.blog-post"
id: 1
}
) {
id
}
}
Example response body
{
"data": {
"reactionToggle": {
"id": 1
}
}
}
// or
{
"data": {
"reactionToggle": {
"id": 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/blog-post/controllers/blog-post.js
'use strict';
/**
* blog-post controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::blog-post.blog-post', ({ strapi }) => ({
async findOne(ctx) {
const response = await super.findOne(ctx);
return strapi
.service('plugin::reactions.enrich')
.enrichOne('api::blog-post.blog-post', response);
},
}));
{
"data":{
"id": 1,
"attributes":{
// Content type attributes
}
},
"meta":{
"reactions":{
"like":[{
"id": 1,
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"relatedUid": "api::blog-post.blog-post:2",
"kind":{
"id": 1,
"name": "Like",
"slug": "like",
"emoji": "👍",
"emojiFallbackUrl": "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f44d.png",
"icon": null
},
"user":{
"id": 1,
"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/blog-post/controllers/blog-post.js
'use strict';
/**
* blog-post controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::blog-post.blog-post', ({ strapi }) => ({
async find(ctx) {
const response = await super.find(ctx);
return strapi
.service('plugin::reactions.enrich')
.enrichMany('api::blog-post.blog-post', response);
},
}));
{
"data": [{
"id": 1,
"attributes":{
// Content type attributes
}
}, {
"id": 2,
"attributes":{
// Content type attributes
}
}
],
"meta":{
"reactions":{
"1": {
"like":[{
"id": 1,
"createdAt": "2023-09-14T20:13:01.649Z",
"updatedAt": "2023-09-14T20:13:01.670Z",
"relatedUid": "api::blog-post.blog-post:2",
"kind":{
"id": 1,
"name": "Like",
"slug": "like",
"emoji": "👍",
"emojiFallbackUrl": "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f44d.png",
"icon": null
},
"user":{
"id": 1,
"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::blog-post.blog-post:1
.
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
Create a soft link in your strapi project to plugin dist
folder
ln -s <your path>/strapi-plugin-reactions/dist <your path>/strapi-project/src/plugins/reactions
Run develop or build command
// Watch for file changes
yarn develop
// or run build without nodemon
yarn build:dev
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 30 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.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.