mirror of
https://github.com/wassname/talk.git
synced 2026-06-27 19:17:09 +08:00
Create Moderation actions notification plugin (#2175)
* Create Moderation actions plugin * Clarify env var in readme * Reword log message * Update with suggestion from Wyatt * Whitelist notification types * Update messaging * Rename unpublished to pending
This commit is contained in:
@@ -45,6 +45,7 @@ plugins/*
|
||||
!plugins/talk-plugin-moderation-actions
|
||||
!plugins/talk-plugin-notifications
|
||||
!plugins/talk-plugin-notifications-category-featured
|
||||
!plugins/talk-plugin-notifications-category-moderation-actions
|
||||
!plugins/talk-plugin-notifications-category-reply
|
||||
!plugins/talk-plugin-notifications-category-staff
|
||||
!plugins/talk-plugin-notifications-digest-daily
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: talk-plugin-notifications-moderation-actions
|
||||
permalink: /plugin/talk-plugin-notifications-moderation-actions/
|
||||
layout: plugin
|
||||
plugin:
|
||||
name: talk-plugin-notifications-moderation-actions
|
||||
depends:
|
||||
- name: talk-plugin-notifications
|
||||
provides:
|
||||
- Server
|
||||
- Client
|
||||
---
|
||||
|
||||
When a comment that is initially withheld from publication and is then
|
||||
approved or rejected, the user will receive a notification email.
|
||||
|
||||
## Configuration:
|
||||
|
||||
- `TALK_MODERATION_NOTIFICATION_TYPES`. This plugin requires values to be set. Available options: `APPROVED`, `REJECTED` as a single string (comma separated).
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "@coralproject/eslint-config-talk/client"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import translations from './translations.yml';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories';
|
||||
|
||||
const SettingsToggle = createSettingsToggle('onModeration', () =>
|
||||
t('talk-plugin-notifications-category-moderation-actions.toggle_description')
|
||||
);
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
notificationSettings: [SettingsToggle],
|
||||
},
|
||||
translations,
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
en:
|
||||
talk-plugin-notifications-category-moderation-actions:
|
||||
toggle_description: My pending comments have been reviewed
|
||||
@@ -0,0 +1,8 @@
|
||||
const MODERATION_NOTIFICATION_TYPES =
|
||||
(process.env.TALK_MODERATION_NOTIFICATION_TYPES &&
|
||||
process.env.TALK_MODERATION_NOTIFICATION_TYPES.split(',')) ||
|
||||
[];
|
||||
|
||||
module.exports = {
|
||||
MODERATION_NOTIFICATION_TYPES,
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
const { get } = require('lodash');
|
||||
const path = require('path');
|
||||
|
||||
const { MODERATION_NOTIFICATION_TYPES } = require('./config');
|
||||
|
||||
const PENDING_STATUS_TYPES = ['PREMOD', 'SYSTEM_WITHHELD'];
|
||||
const AVAILABLE_NOTIFICATION_TYPES = ['APPROVED', 'REJECTED'];
|
||||
|
||||
const handle = async (ctx, comment) => {
|
||||
const commentID = get(comment, 'id', null);
|
||||
if (commentID === null) {
|
||||
ctx.log.info('could not get comment id');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if this was a pending comment.
|
||||
const commentHistory = get(comment, 'status_history', []);
|
||||
let wasPending = false;
|
||||
|
||||
// Check for last status before current one
|
||||
if (commentHistory.length >= 2) {
|
||||
const previousStatus = commentHistory[commentHistory.length - 2];
|
||||
if (PENDING_STATUS_TYPES.includes(previousStatus.type)) {
|
||||
wasPending = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasPending) {
|
||||
ctx.log.info('comment was not pending');
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the graph request.
|
||||
const commentQl = await ctx.graphql(
|
||||
`
|
||||
query GetAuthorUserMetadata($comment_id: ID!) {
|
||||
comment(id: $comment_id) {
|
||||
id
|
||||
user {
|
||||
id
|
||||
notificationSettings {
|
||||
onModeration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ comment_id: commentID }
|
||||
);
|
||||
if (commentQl.errors) {
|
||||
ctx.log.error(
|
||||
{ err: commentQl.errors },
|
||||
'could not query for author metadata'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user has notifications enabled.
|
||||
const enabled = get(
|
||||
commentQl,
|
||||
'data.comment.user.notificationSettings.onModeration',
|
||||
false
|
||||
);
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userID = get(commentQl, 'data.comment.user.id', null);
|
||||
if (!userID) {
|
||||
ctx.log.info('could not get comment user id');
|
||||
return;
|
||||
}
|
||||
|
||||
// The user does have notifications for moderated comments enabled, queue the
|
||||
// notification to be sent.
|
||||
return { userID, date: comment.created_at, context: comment.id };
|
||||
};
|
||||
|
||||
const hydrate = async (ctx, category, context) => {
|
||||
const comment = await ctx.graphql(
|
||||
`
|
||||
query GetNotificationData($context: ID!) {
|
||||
comment(id: $context) {
|
||||
id
|
||||
asset {
|
||||
title
|
||||
url
|
||||
}
|
||||
status_history {
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ context }
|
||||
);
|
||||
if (comment.errors) {
|
||||
throw comment.errors;
|
||||
}
|
||||
|
||||
const commentData = get(comment, 'data.comment');
|
||||
|
||||
const headline = get(commentData, 'asset.title', null);
|
||||
const assetURL = get(commentData, 'asset.url', null);
|
||||
const permalink = `${assetURL}?commentId=${commentData.id}`;
|
||||
return [headline, permalink];
|
||||
};
|
||||
|
||||
const handlers = {
|
||||
approved: {
|
||||
handle,
|
||||
category: 'moderation-actions.approved',
|
||||
event: 'commentAccepted',
|
||||
hydrate,
|
||||
digestOrder: 10,
|
||||
},
|
||||
rejected: {
|
||||
handle,
|
||||
category: 'moderation-actions.rejected',
|
||||
event: 'commentRejected',
|
||||
hydrate,
|
||||
digestOrder: 10,
|
||||
},
|
||||
};
|
||||
|
||||
const notifications = [];
|
||||
MODERATION_NOTIFICATION_TYPES.forEach(type => {
|
||||
if (AVAILABLE_NOTIFICATION_TYPES.includes(type)) {
|
||||
notifications.push(handlers[type.toLowerCase()]);
|
||||
} else {
|
||||
console.error(`Unkown moderation notification type: ${type}`);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
typeDefs: `
|
||||
type NotificationSettings {
|
||||
onModeration: Boolean!
|
||||
}
|
||||
|
||||
input NotificationSettingsInput {
|
||||
onModeration: Boolean
|
||||
}
|
||||
`,
|
||||
resolvers: {
|
||||
NotificationSettings: {
|
||||
// onModeration returns false by default if not specified.
|
||||
onModeration: settings => get(settings, 'onModeration', false),
|
||||
},
|
||||
},
|
||||
translations: path.join(__dirname, 'translations.yml'),
|
||||
notifications,
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
en:
|
||||
talk-plugin-notifications:
|
||||
categories:
|
||||
moderation-actions:
|
||||
approved:
|
||||
subject: "Your comment on {0} has been published"
|
||||
body: "{0}\nThank you for submitting your comment. Your comment has now been published: {1}"
|
||||
rejected:
|
||||
subject: "Your comment on {0} was not published"
|
||||
body: "{0}\nThe language used in one of your comments did not comply with our community guidelines, and so the comment has been removed."
|
||||
Reference in New Issue
Block a user