mirror of
https://github.com/wassname/talk.git
synced 2026-07-13 11:29:55 +08:00
Merge branch 'trust-merge' into user-detail-ui
This commit is contained in:
+100
-1
@@ -1,12 +1,91 @@
|
||||
const debug = require('debug')('talk:graph:mutators:comment');
|
||||
const errors = require('../../errors');
|
||||
|
||||
const ActionModel = require('../../models/action');
|
||||
const AssetsService = require('../../services/assets');
|
||||
const ActionsService = require('../../services/actions');
|
||||
const CommentsService = require('../../services/comments');
|
||||
const KarmaService = require('../../services/karma');
|
||||
const linkify = require('linkify-it')();
|
||||
|
||||
const Wordlist = require('../../services/wordlist');
|
||||
|
||||
/**
|
||||
* adjustKarma will adjust the affected user's karma depending on the moderators
|
||||
* action.
|
||||
*/
|
||||
const adjustKarma = (Comments, id, status) => async () => {
|
||||
try {
|
||||
|
||||
// Use the dataloader to get the comment that was just moderated and
|
||||
// get the flag user's id's so we can adjust their karma too.
|
||||
let [
|
||||
comment,
|
||||
flagUserIDs
|
||||
] = await Promise.all([
|
||||
|
||||
// Load the comment that was just made/updated by the setCommentStatus
|
||||
// operation.
|
||||
Comments.get.load(id),
|
||||
|
||||
// Find all the flag actions that were referenced by this comment
|
||||
// at this point in time.
|
||||
ActionModel.find({
|
||||
item_id: id,
|
||||
item_type: 'COMMENTS',
|
||||
action_type: 'FLAG'
|
||||
}).then((actions) => {
|
||||
|
||||
// This is to ensure that this is always an array.
|
||||
if (!actions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return actions.map(({user_id}) => user_id);
|
||||
})
|
||||
]);
|
||||
|
||||
debug(`Comment[${id}] by User[${comment.author_id}] was Status[${status}]`);
|
||||
|
||||
switch (status) {
|
||||
case 'REJECTED':
|
||||
|
||||
// Reduce the user's karma.
|
||||
debug(`CommentUser[${comment.author_id}] had their karma reduced`);
|
||||
|
||||
// Decrease the flag user's karma, the moderator disagreed with this
|
||||
// action.
|
||||
debug(`FlaggingUser[${flagUserIDs.join(', ')}] had their karma increased`);
|
||||
await Promise.all([
|
||||
KarmaService.modifyUser(comment.author_id, -1, 'comment'),
|
||||
KarmaService.modifyUser(flagUserIDs, 1, 'flag', true)
|
||||
]);
|
||||
|
||||
break;
|
||||
|
||||
case 'ACCEPTED':
|
||||
|
||||
// Increase the user's karma.
|
||||
debug(`CommentUser[${comment.author_id}] had their karma increased`);
|
||||
|
||||
// Increase the flag user's karma, the moderator agreed with this
|
||||
// action.
|
||||
debug(`FlaggingUser[${flagUserIDs.join(', ')}] had their karma reduced`);
|
||||
await Promise.all([
|
||||
KarmaService.modifyUser(comment.author_id, 1, 'comment'),
|
||||
KarmaService.modifyUser(flagUserIDs, -1, 'flag', true)
|
||||
]);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new comment.
|
||||
* @param {Object} user the user performing the request
|
||||
@@ -86,6 +165,7 @@ const filterNewComment = (context, {body, asset_id}) => {
|
||||
* @return {Promise} resolves to the comment's status
|
||||
*/
|
||||
const resolveNewCommentStatus = async (context, {asset_id, body}, wordlist = {}, settings = {}) => {
|
||||
let {user} = context;
|
||||
|
||||
// Check to see if the body is too short, if it is, then complain about it!
|
||||
if (body.length < 2) {
|
||||
@@ -123,6 +203,22 @@ const resolveNewCommentStatus = async (context, {asset_id, body}, wordlist = {},
|
||||
return 'REJECTED';
|
||||
}
|
||||
|
||||
if (user && user.metadata) {
|
||||
|
||||
// If the user is not a reliable commenter (passed the unreliability
|
||||
// threshold by having too many rejected comments) then we can change the
|
||||
// status of the comment to `PREMOD`, therefore pushing the user's comments
|
||||
// away from the public eye until a moderator can manage them. This of
|
||||
// course can only be applied if the comment's current status is `NONE`,
|
||||
// we don't want to interfere if the comment was rejected.
|
||||
if (KarmaService.isReliable('comment', user.metadata.trust) === false) {
|
||||
|
||||
// Update the response from the comment creation to add the PREMOD so that
|
||||
// that user's UI will reflect the fact that their comment is in pre-mod.
|
||||
return 'PREMOD';
|
||||
}
|
||||
}
|
||||
|
||||
return moderation === 'PRE' ? 'PREMOD' : 'NONE';
|
||||
};
|
||||
|
||||
@@ -179,7 +275,6 @@ const createPublicComment = async (context, commentInput) => {
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
* @param {String} status the new status of the comment
|
||||
*/
|
||||
|
||||
const setStatus = async ({user, loaders: {Comments}}, {id, status}) => {
|
||||
let comment = await CommentsService.pushStatus(id, status, user ? user.id : null);
|
||||
|
||||
@@ -196,6 +291,10 @@ const setStatus = async ({user, loaders: {Comments}}, {id, status}) => {
|
||||
|
||||
Comments.countByAssetID.clear(comment.asset_id);
|
||||
|
||||
// postSetCommentStatus will use the arguments from the mutation and
|
||||
// adjust the affected user's karma in the next tick.
|
||||
process.nextTick(adjustKarma(Comments, id, status));
|
||||
|
||||
return comment;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const KarmaService = require('../../services/karma');
|
||||
|
||||
const User = {
|
||||
action_summaries({id}, _, {loaders: {Actions}}) {
|
||||
return Actions.getSummariesByItemID.load(id);
|
||||
@@ -59,6 +61,13 @@ const User = {
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
// Extract the reliability from the user metadata if they have permission.
|
||||
reliable(user, _, {user: requestingUser}) {
|
||||
if (requestingUser && requestingUser.hasRoles('ADMIN')) {
|
||||
return KarmaService.model(user);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,22 @@
|
||||
# Date represented as an ISO8601 string.
|
||||
scalar Date
|
||||
|
||||
################################################################################
|
||||
## Reliability
|
||||
################################################################################
|
||||
|
||||
# Reliability defines how a given user should be considered reliable for their
|
||||
# comment or flag activity.
|
||||
type Reliability {
|
||||
|
||||
# flagger will be `true` when the flagger is reliable, `false` if not, or
|
||||
# `null` if the reliability cannot be determined.
|
||||
flagger: Boolean
|
||||
|
||||
# commenter will be `true` when the commenter is reliable, `false` if not, or
|
||||
# `null` if the reliability cannot be determined.
|
||||
commenter: Boolean
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Users
|
||||
@@ -62,6 +78,11 @@ type User {
|
||||
# returns all comments based on a query.
|
||||
comments(query: CommentsQuery): [Comment!]
|
||||
|
||||
# reliable is the reference to a given user's Reliability. If the requesting
|
||||
# user does not have permission to access the reliability, null will be
|
||||
# returned.
|
||||
reliable: Reliability
|
||||
|
||||
# returns user status
|
||||
status: USER_STATUS
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
const debug = require('debug')('talk:trust');
|
||||
const UserModel = require('../models/user');
|
||||
|
||||
/**
|
||||
* This will create an object with the property name of the action type as the
|
||||
* key and an object as it's value. This will contain a RELIABLE, and UNRELIABLE
|
||||
* property with the number of karma points associated with their particular
|
||||
* state.
|
||||
*
|
||||
* If only the RELIABLE variable is provided, then it will also be used as the
|
||||
* UNRELIABLE variable.
|
||||
*
|
||||
* The form of the environment variable is:
|
||||
*
|
||||
* <name>:<RELIABLE>,<UNRELIABLE>;<name>:<RELIABLE>,<UNRELIABLE>;...
|
||||
*
|
||||
* The default used is:
|
||||
*
|
||||
* comment:1,1;flag:-1,-1
|
||||
*/
|
||||
const parseThresholds = (thresholds) => thresholds
|
||||
.split(';')
|
||||
.filter((threshold) => threshold && threshold.length > 0)
|
||||
.reduce((acc, threshold) => {
|
||||
const thresholds = threshold.split(':');
|
||||
if (thresholds.length < 2) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
let [name, values] = thresholds;
|
||||
let [RELIABLE, UNRELIABLE] = values.split(',').map((value) => parseInt(value));
|
||||
|
||||
if (!(name in acc)) {
|
||||
acc[name] = {};
|
||||
}
|
||||
|
||||
if (isNaN(UNRELIABLE) && !isNaN(RELIABLE)) {
|
||||
acc[name].RELIABLE = RELIABLE;
|
||||
acc[name].UNRELIABLE = RELIABLE;
|
||||
} else {
|
||||
if (!isNaN(UNRELIABLE)) {
|
||||
acc[name].UNRELIABLE = UNRELIABLE;
|
||||
}
|
||||
|
||||
if (!isNaN(RELIABLE)) {
|
||||
acc[name].RELIABLE = RELIABLE;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {
|
||||
comment: {
|
||||
RELIABLE: -1,
|
||||
UNRELIABLE: -1
|
||||
},
|
||||
flag: {
|
||||
RELIABLE: -1,
|
||||
UNRELIABLE: -1
|
||||
}
|
||||
});
|
||||
|
||||
const THRESHOLDS = parseThresholds(process.env.TRUST_THRESHOLDS || '');
|
||||
|
||||
debug('using thresholds: ', THRESHOLDS);
|
||||
|
||||
/**
|
||||
* KarmaModel represents the checkable properties of a user and wrapps the
|
||||
* KarmaService function `isReliable` to work flexibly with the graph.
|
||||
*/
|
||||
class KarmaModel {
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
get flagger() {
|
||||
return KarmaService.isReliable('flag', this.model);
|
||||
}
|
||||
|
||||
get commenter() {
|
||||
return KarmaService.isReliable('comment', this.model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* KarmaService provides interfaces for editing a user's karma.
|
||||
*/
|
||||
class KarmaService {
|
||||
|
||||
/**
|
||||
* Model returns a KarmaModel based on the passed in user.
|
||||
*/
|
||||
static model(user) {
|
||||
if (user === null || !user.metadata || !user.metadata.trust) {
|
||||
return new KarmaModel({});
|
||||
}
|
||||
|
||||
return new KarmaModel(user.metadata.trust);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the reliability of a property and returns it if known.
|
||||
* @param {String} name - name of the property
|
||||
* @param {Object} trust - object possibly containing the propertys
|
||||
*/
|
||||
static isReliable(name, trust) {
|
||||
if (trust && trust[name]) {
|
||||
if (trust[name].karma > THRESHOLDS[name].RELIABLE) {
|
||||
return true;
|
||||
} else if (trust[name].karma < THRESHOLDS[name].UNRELIABLE) {
|
||||
return false;
|
||||
}
|
||||
} else if (THRESHOLDS[name].RELIABLE < 0) {
|
||||
return true;
|
||||
} else if (THRESHOLDS[name].UNRELIABLE > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* modifyUserKarma updates the user to adjust their karma, for either the `type`
|
||||
* of 'comment' or 'flag'. If `multi` is true, then it assumes that `id` is an
|
||||
* array of id's.
|
||||
*/
|
||||
static async modifyUser(id, direction = 1, type = 'comment', multi = false) {
|
||||
const key = `metadata.trust.${type}.karma`;
|
||||
|
||||
let update = {
|
||||
$inc: {
|
||||
[key]: direction
|
||||
}
|
||||
};
|
||||
|
||||
if (multi) {
|
||||
|
||||
// If it was in multi-mode but there was no user's to adjust, bail.
|
||||
if (id.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return UserModel.update({
|
||||
id: {
|
||||
$in: id
|
||||
}
|
||||
}, update, {
|
||||
multi: true
|
||||
});
|
||||
}
|
||||
|
||||
return UserModel.update({id}, update);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = KarmaService;
|
||||
Reference in New Issue
Block a user