mirror of
https://github.com/wassname/talk.git
synced 2026-07-17 11:33:39 +08:00
Merge branch 'master' of github.com:coralproject/talk into plugin_examples
This commit is contained in:
@@ -6,6 +6,7 @@ const {
|
||||
const DataLoader = require('dataloader');
|
||||
|
||||
const CommentModel = require('../../models/comment');
|
||||
const UsersService = require('../../services/users');
|
||||
|
||||
/**
|
||||
* Returns the comment count for all comments that are public based on their
|
||||
@@ -39,6 +40,31 @@ const getCountsByAssetID = (context, asset_ids) => {
|
||||
.then((results) => results.map((result) => result ? result.count : 0));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the count of all public comments on an asset id, also filtering by personalization options.
|
||||
*
|
||||
* @param {Array<String>} id The ID of the asset
|
||||
* @param {Array<String>} excludeIgnored Exclude comments ignored by the requesting user
|
||||
*/
|
||||
const getCountsByAssetIDPersonalized = async (context, {assetId, excludeIgnored}) => {
|
||||
const query = {
|
||||
asset_id: assetId,
|
||||
status: {
|
||||
$in: ['NONE', 'ACCEPTED'],
|
||||
},
|
||||
};
|
||||
const user = context.user;
|
||||
if (excludeIgnored && user) {
|
||||
|
||||
// load afresh, as `user` may be from cache and not have recent ignores
|
||||
const freshUser = await UsersService.findById(user.id);
|
||||
const ignoredUsers = freshUser.ignoresUsers;
|
||||
query.author_id = {$nin: ignoredUsers};
|
||||
}
|
||||
const count = await CommentModel.where(query).count();
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the comment count for all comments that are public based on their
|
||||
* asset ids.
|
||||
@@ -72,6 +98,32 @@ const getParentCountsByAssetID = (context, asset_ids) => {
|
||||
.then((results) => results.map((result) => result ? result.count : 0));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the count of top-level comments on an asset id, also filtering by personalization options.
|
||||
*
|
||||
* @param {Array<String>} id The ID of the asset
|
||||
* @param {Array<String>} excludeIgnored Exclude comments ignored by the requesting user
|
||||
*/
|
||||
const getParentCountByAssetIDPersonalized = async (context, {assetId, excludeIgnored}) => {
|
||||
const query = {
|
||||
asset_id: assetId,
|
||||
parent_id: null,
|
||||
status: {
|
||||
$in: ['NONE', 'ACCEPTED'],
|
||||
},
|
||||
};
|
||||
const user = context.user;
|
||||
if (excludeIgnored && user) {
|
||||
|
||||
// load afresh, as `user` may be from cache and not have recent ignores
|
||||
const freshUser = await UsersService.findById(user.id);
|
||||
const ignoredUsers = freshUser.ignoresUsers;
|
||||
query.author_id = {$nin: ignoredUsers};
|
||||
}
|
||||
const count = await CommentModel.where(query).count();
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the comment count for all comments that are public based on their
|
||||
* parent ids.
|
||||
@@ -104,6 +156,33 @@ const getCountsByParentID = (context, parent_ids) => {
|
||||
.then((results) => results.map((result) => result ? result.count : 0));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the count of comments for the provided parent_id, also filtering by personalization options.
|
||||
*
|
||||
* @param {Array<String>} id The ID of the parent comment
|
||||
* @param {Array<String>} excludeIgnored Exclude comments ignored by context.user
|
||||
*/
|
||||
const getCountByParentIDPersonalized = async (context, {id, excludeIgnored}) => {
|
||||
const query = {
|
||||
parent_id: {
|
||||
$in: [id]
|
||||
},
|
||||
status: {
|
||||
$in: ['NONE', 'ACCEPTED']
|
||||
}
|
||||
};
|
||||
const user = context.user;
|
||||
if (excludeIgnored && user) {
|
||||
|
||||
// load afresh, as `user` may be from cache and not have recent ignores
|
||||
const freshUser = await UsersService.findById(user.id);
|
||||
const ignoredUsers = freshUser.ignoresUsers;
|
||||
query.author_id = {$nin: ignoredUsers};
|
||||
}
|
||||
const count = await CommentModel.where(query).count();
|
||||
return count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the count of comments based on the passed in query.
|
||||
* @param {Object} context graph context
|
||||
@@ -142,7 +221,7 @@ const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id}) =
|
||||
* @param {Object} context graph context
|
||||
* @param {Object} query query terms to apply to the comments query
|
||||
*/
|
||||
const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, author_id, limit, cursor, sort}) => {
|
||||
const getCommentsByQuery = async ({user}, {ids, statuses, asset_id, parent_id, author_id, limit, cursor, sort, excludeIgnored}) => {
|
||||
let comments = CommentModel.find();
|
||||
|
||||
// Only administrators can search for comments with statuses that are not
|
||||
@@ -184,6 +263,16 @@ const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, author_
|
||||
comments = comments.where({parent_id});
|
||||
}
|
||||
|
||||
if (excludeIgnored && user) {
|
||||
|
||||
// load afresh, as `user` may be from cache and not have recent ignores
|
||||
const freshUser = await UsersService.findById(user.id);
|
||||
const ignoredUsers = freshUser.ignoresUsers;
|
||||
comments = comments.where({
|
||||
author_id: {$nin: ignoredUsers}
|
||||
});
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
if (sort === 'REVERSE_CHRONOLOGICAL') {
|
||||
comments = comments.where({
|
||||
@@ -344,8 +433,11 @@ module.exports = (context) => ({
|
||||
getByQuery: (query) => getCommentsByQuery(context, query),
|
||||
getCountByQuery: (query) => getCommentCountByQuery(context, query),
|
||||
countByAssetID: new SharedCounterDataLoader('Comments.totalCommentCount', 3600, (ids) => getCountsByAssetID(context, ids)),
|
||||
countByAssetIDPersonalized: (query) => getCountsByAssetIDPersonalized(context, query),
|
||||
parentCountByAssetID: new SharedCounterDataLoader('Comments.countByAssetID', 3600, (ids) => getParentCountsByAssetID(context, ids)),
|
||||
parentCountByAssetIDPersonalized: (query) => getParentCountByAssetIDPersonalized(context, query),
|
||||
countByParentID: new SharedCounterDataLoader('Comments.countByParentID', 3600, (ids) => getCountsByParentID(context, ids)),
|
||||
countByParentIDPersonalized: (query) => getCountByParentIDPersonalized(context, query),
|
||||
genRecentReplies: new DataLoader((ids) => genRecentReplies(context, ids)),
|
||||
genRecentComments: new DataLoader((ids) => genRecentComments(context, ids))
|
||||
}
|
||||
|
||||
+11
-1
@@ -13,11 +13,21 @@ const suspendUser = ({user}, {id, message}) => {
|
||||
});
|
||||
};
|
||||
|
||||
const ignoreUser = async ({user}, userToIgnore) => {
|
||||
return await UsersService.ignoreUsers(user.id, [userToIgnore.id]);
|
||||
};
|
||||
|
||||
const stopIgnoringUser = async ({user}, userToStopIgnoring) => {
|
||||
return await UsersService.stopIgnoringUsers(user.id, [userToStopIgnoring.id]);
|
||||
};
|
||||
|
||||
module.exports = (context) => {
|
||||
let mutators = {
|
||||
User: {
|
||||
setUserStatus: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
suspendUser: () => Promise.reject(errors.ErrNotAuthorized)
|
||||
suspendUser: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
ignoreUser: (action) => ignoreUser(context, action),
|
||||
stopIgnoringUser: (action) => stopIgnoringUser(context, action),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,26 +9,31 @@ const Asset = {
|
||||
recentComments({id}, _, {loaders: {Comments}}) {
|
||||
return Comments.genRecentComments.load(id);
|
||||
},
|
||||
comments({id}, {sort, limit}, {loaders: {Comments}}) {
|
||||
comments({id}, {sort, limit, excludeIgnored}, {loaders: {Comments}}) {
|
||||
return Comments.getByQuery({
|
||||
asset_id: id,
|
||||
sort,
|
||||
limit,
|
||||
parent_id: null
|
||||
parent_id: null,
|
||||
excludeIgnored,
|
||||
});
|
||||
},
|
||||
commentCount({id, commentCount}, _, {loaders: {Comments}}) {
|
||||
commentCount({id, commentCount}, {excludeIgnored}, {user, loaders: {Comments}}) {
|
||||
if (user && excludeIgnored) {
|
||||
return Comments.parentCountByAssetIDPersonalized({assetId: id, excludeIgnored});
|
||||
}
|
||||
if (commentCount != null) {
|
||||
return commentCount;
|
||||
}
|
||||
|
||||
return Comments.parentCountByAssetID.load(id);
|
||||
},
|
||||
totalCommentCount({id, totalCommentCount}, _, {loaders: {Comments}}) {
|
||||
totalCommentCount({id, totalCommentCount}, {excludeIgnored}, {user, loaders: {Comments}}) {
|
||||
if (user && excludeIgnored) {
|
||||
return Comments.countByAssetIDPersonalized({assetId: id, excludeIgnored});
|
||||
}
|
||||
if (totalCommentCount != null) {
|
||||
return totalCommentCount;
|
||||
}
|
||||
|
||||
return Comments.countByAssetID.load(id);
|
||||
},
|
||||
settings({settings = null}, _, {loaders: {Settings}}) {
|
||||
|
||||
@@ -12,16 +12,20 @@ const Comment = {
|
||||
recentReplies({id}, _, {loaders: {Comments}}) {
|
||||
return Comments.genRecentReplies.load(id);
|
||||
},
|
||||
replies({id, asset_id}, {sort, limit}, {loaders: {Comments}}) {
|
||||
replies({id, asset_id}, {sort, limit, excludeIgnored}, {loaders: {Comments}}) {
|
||||
return Comments.getByQuery({
|
||||
asset_id,
|
||||
parent_id: id,
|
||||
sort,
|
||||
limit
|
||||
limit,
|
||||
excludeIgnored,
|
||||
});
|
||||
},
|
||||
replyCount({id}, _, {loaders: {Comments}}) {
|
||||
return Comments.countByParentID.load(id);
|
||||
replyCount({id}, {excludeIgnored}, {user, loaders: {Comments}}) {
|
||||
if (user && excludeIgnored) {
|
||||
return Comments.countByParentIDPersonalized({id, excludeIgnored});
|
||||
}
|
||||
return Comments.countByParentID.load(id);
|
||||
},
|
||||
actions({id}, _, {user, loaders: {Actions}}) {
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@ const RootMutation = {
|
||||
suspendUser(_, {id, message}, {mutators: {User}}) {
|
||||
return wrapResponse(null)(User.suspendUser({id, message}));
|
||||
},
|
||||
ignoreUser(_, {id}, {mutators: {User}}) {
|
||||
return wrapResponse(null)(User.ignoreUser({id}));
|
||||
},
|
||||
stopIgnoringUser(_, {id}, {mutators: {User}}) {
|
||||
return wrapResponse(null)(User.stopIgnoringUser({id}));
|
||||
},
|
||||
setCommentStatus(_, {id, status}, {mutators: {Comment}}) {
|
||||
return wrapResponse(null)(Comment.setCommentStatus({id, status}));
|
||||
},
|
||||
|
||||
@@ -19,15 +19,15 @@ const RootQuery = {
|
||||
|
||||
// This endpoint is used for loading moderation queues, so hide it in the
|
||||
// event that we aren't an admin.
|
||||
comments(_, {query: {action_type, statuses, asset_id, parent_id, limit, cursor, sort}}, {user, loaders: {Comments, Actions}}) {
|
||||
let query = {statuses, asset_id, parent_id, limit, cursor, sort};
|
||||
comments(_, {query: {action_type, statuses, asset_id, parent_id, limit, cursor, sort, excludeIgnored}}, {user, loaders: {Comments, Actions}}) {
|
||||
let query = {statuses, asset_id, parent_id, limit, cursor, sort, excludeIgnored};
|
||||
|
||||
if (user != null && user.hasRoles('ADMIN') && action_type) {
|
||||
return Actions.getByTypes({action_type, item_type: 'COMMENTS'})
|
||||
.then((ids) => {
|
||||
|
||||
// Perform the query using the available resolver.
|
||||
return Comments.getByQuery({ids, statuses, asset_id, parent_id, limit, cursor, sort});
|
||||
return Comments.getByQuery({ids, statuses, asset_id, parent_id, limit, cursor, sort, excludeIgnored});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,6 +83,16 @@ const RootQuery = {
|
||||
return user;
|
||||
},
|
||||
|
||||
myIgnoredUsers: async (_, args, {user, loaders: {Users}}) => {
|
||||
|
||||
// get currentUser again since context.user was out of date when running test/graph/mutations/ignoreUser
|
||||
const currentUser = (await Users.getByQuery({ids: [user.id], limit: 1}))[0];
|
||||
if ( ! (currentUser && Array.isArray(currentUser.ignoresUsers) && currentUser.ignoresUsers.length)) {
|
||||
return [];
|
||||
}
|
||||
return await Users.getByQuery({ids: currentUser.ignoresUsers});
|
||||
},
|
||||
|
||||
// This endpoint is used for loading the user moderation queues (users whose username has been flagged),
|
||||
// so hide it in the event that we aren't an admin.
|
||||
users(_, {query: {action_type, limit, cursor, sort}}, {user, loaders: {Users, Actions}}) {
|
||||
|
||||
+31
-7
@@ -140,6 +140,9 @@ input CommentsQuery {
|
||||
|
||||
# Sort the results by created_at.
|
||||
sort: SORT_ORDER = REVERSE_CHRONOLOGICAL
|
||||
|
||||
# Exclude comments ignored by the requesting user
|
||||
excludeIgnored: Boolean
|
||||
}
|
||||
|
||||
# CommentCountQuery allows the ability to query comment counts by specific
|
||||
@@ -185,10 +188,10 @@ type Comment {
|
||||
recentReplies: [Comment]
|
||||
|
||||
# the replies that were made to the comment.
|
||||
replies(sort: SORT_ORDER = CHRONOLOGICAL, limit: Int = 3): [Comment]
|
||||
replies(sort: SORT_ORDER = CHRONOLOGICAL, limit: Int = 3, excludeIgnored: Boolean): [Comment]
|
||||
|
||||
# The count of replies on a comment.
|
||||
replyCount: Int
|
||||
replyCount(excludeIgnored: Boolean): Int
|
||||
|
||||
# Actions completed on the parent. Requires the `ADMIN` role.
|
||||
actions: [Action]
|
||||
@@ -420,13 +423,13 @@ type Asset {
|
||||
recentComments: [Comment]
|
||||
|
||||
# The top level comments that are attached to the asset.
|
||||
comments(sort: SORT_ORDER = REVERSE_CHRONOLOGICAL, limit: Int = 10): [Comment]
|
||||
comments(sort: SORT_ORDER = REVERSE_CHRONOLOGICAL, limit: Int = 10, excludeIgnored: Boolean): [Comment]
|
||||
|
||||
# The count of top level comments on the asset.
|
||||
commentCount: Int
|
||||
commentCount(excludeIgnored: Boolean): Int
|
||||
|
||||
# The total count of all comments made on the asset.
|
||||
totalCommentCount: Int
|
||||
totalCommentCount(excludeIgnored: Boolean): Int
|
||||
|
||||
# The settings (rectified with the global settings) that should be applied to
|
||||
# this asset.
|
||||
@@ -540,15 +543,18 @@ type RootQuery {
|
||||
# role.
|
||||
me: User
|
||||
|
||||
# Users that the currently logged in user ignores
|
||||
myIgnoredUsers: [User]
|
||||
|
||||
# Users returned based on a query.
|
||||
users(query: UsersQuery): [User]
|
||||
|
||||
# Asset metrics related to user actions are saturated into the assets
|
||||
# returned.
|
||||
# returned. Parameters `from` and `to` are related to the action created_at field.
|
||||
assetMetrics(from: Date!, to: Date!, sort: ASSET_METRICS_SORT!, limit: Int = 10): [Asset!]
|
||||
|
||||
# Comment metrics related to user actions are saturated into the comments
|
||||
# returned.
|
||||
# returned. Parameters `from` and `to` are related to the action created_at field.
|
||||
commentMetrics(from: Date!, to: Date!, sort: ACTION_TYPE!, limit: Int = 10): [Comment!]
|
||||
}
|
||||
|
||||
@@ -726,6 +732,18 @@ type RemoveCommentTagResponse implements Response {
|
||||
errors: [UserError]
|
||||
}
|
||||
|
||||
# Response to ignoreUser mutation
|
||||
type IgnoreUserResponse implements Response {
|
||||
# An array of errors relating to the mutation that occured.
|
||||
errors: [UserError]
|
||||
}
|
||||
|
||||
# Response to stopIgnoringUser mutation
|
||||
type StopIgnoringUserResponse implements Response {
|
||||
# An array of errors relating to the mutation that occured.
|
||||
errors: [UserError]
|
||||
}
|
||||
|
||||
# All mutations for the application are defined on this object.
|
||||
type RootMutation {
|
||||
|
||||
@@ -758,6 +776,12 @@ type RootMutation {
|
||||
|
||||
# Remove tag from comment.
|
||||
removeCommentTag(id: ID!, tag: String!): RemoveCommentTagResponse
|
||||
|
||||
# Ignore comments by another user
|
||||
ignoreUser(id: ID!): IgnoreUserResponse
|
||||
|
||||
# Stop Ignoring comments by another user
|
||||
stopIgnoringUser(id: ID!): StopIgnoringUserResponse
|
||||
}
|
||||
|
||||
################################################################################
|
||||
|
||||
Reference in New Issue
Block a user