mirror of
https://github.com/wassname/talk.git
synced 2026-07-16 11:22:16 +08:00
Implemented sortBy
This commit is contained in:
@@ -56,7 +56,7 @@ const extension = {
|
||||
fragment CoralEmbedStream_CreateCommentResponse on CreateCommentResponse {
|
||||
comment {
|
||||
...CoralEmbedStream_CreateCommentResponse_Comment
|
||||
replies {
|
||||
replies(query: {}) {
|
||||
nodes {
|
||||
...CoralEmbedStream_CreateCommentResponse_Comment
|
||||
}
|
||||
|
||||
+15
-7
@@ -1,6 +1,7 @@
|
||||
const loaders = require('./loaders');
|
||||
const mutators = require('./mutators');
|
||||
const uuid = require('uuid');
|
||||
const merge = require('lodash/merge');
|
||||
|
||||
const plugins = require('../services/plugins');
|
||||
const pubsub = require('../services/pubsub');
|
||||
@@ -17,17 +18,24 @@ const contextPlugins = plugins.get('server', 'context').map(({plugin, context})
|
||||
});
|
||||
|
||||
/**
|
||||
* This should itterate over the passed in plugins and load them all with the
|
||||
* This should iterate over the passed in plugins and load them all with the
|
||||
* current graph context.
|
||||
* @return {Object} the saturated plugins object
|
||||
*/
|
||||
const decorateContextPlugins = (context, contextPlugins) => contextPlugins.reduce((acc, plugin) => {
|
||||
Object.keys(plugin.context).forEach((service) => {
|
||||
acc[service] = plugin.context[service](context);
|
||||
});
|
||||
const decorateContextPlugins = (context, contextPlugins) => {
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
// For each of the plugins, we execute with the context to get the context
|
||||
// based plugin. We then merge that into an object for the plugin. Once the
|
||||
// plugin is assembled, we merge that object with all the other objects
|
||||
// provided from the other plugins.
|
||||
return merge(...contextPlugins.map((plugin) => {
|
||||
return Object.keys(plugin.context).reduce((services, serviceName) => {
|
||||
services[serviceName] = plugin.context[serviceName](context);
|
||||
|
||||
return services;
|
||||
}, {});
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores the request context.
|
||||
|
||||
+149
-41
@@ -133,18 +133,156 @@ const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id, au
|
||||
.count();
|
||||
};
|
||||
|
||||
/**
|
||||
* getStartCursor will retrieve the start cursor based on the sortBy field.
|
||||
*
|
||||
* @param {Object} ctx the graph context
|
||||
* @param {Object} nodes the result set of retrieved comments
|
||||
* @param {Object} params the params from the client describing the query
|
||||
*/
|
||||
const getStartCursor = (ctx, nodes, {cursor, sortBy}) => {
|
||||
switch (sortBy) {
|
||||
case 'CREATED_AT':
|
||||
return nodes.length ? nodes[0].created_at : null;
|
||||
case 'REPLIES':
|
||||
|
||||
// The cursor is the start! This is using numeric pagination.
|
||||
return cursor != null ? cursor : 0;
|
||||
}
|
||||
|
||||
const SORT_KEY = sortBy.toLowerCase();
|
||||
if (!ctx.plugins || !ctx.plugins.CommentSort || !ctx.plugins.CommentSort[SORT_KEY] || !ctx.plugins.CommentSort[SORT_KEY].startCursor) {
|
||||
throw new Error(`unable to sort by ${sortBy}, no plugin was provided to handle this type`);
|
||||
}
|
||||
|
||||
return ctx.plugins.CommentSort[SORT_KEY].startCursor(ctx, nodes, {cursor});
|
||||
};
|
||||
|
||||
/**
|
||||
* getEndCursor will fetch the end cursor based on the desired sortBy parameter.
|
||||
*
|
||||
* @param {Object} ctx the graph context
|
||||
* @param {Object} nodes the result set of retrieved comments
|
||||
* @param {Object} params the params from the client describing the query
|
||||
*/
|
||||
const getEndCursor = (ctx, nodes, {cursor, sortBy}) => {
|
||||
switch (sortBy) {
|
||||
case 'CREATED_AT':
|
||||
return nodes.length ? nodes[nodes.length - 1].created_at : null;
|
||||
case 'REPLIES':
|
||||
return nodes.length ? (cursor != null ? cursor : 0) + nodes.length : null;
|
||||
}
|
||||
|
||||
const SORT_KEY = sortBy.toLowerCase();
|
||||
if (!ctx.plugins || !ctx.plugins.CommentSort || !ctx.plugins.CommentSort[SORT_KEY] || !ctx.plugins.CommentSort[SORT_KEY].endCursor) {
|
||||
throw new Error(`unable to sort by ${sortBy}, no plugin was provided to handle this type`);
|
||||
}
|
||||
|
||||
return ctx.plugins.CommentSort[SORT_KEY].endCursor(ctx, nodes, {cursor});
|
||||
};
|
||||
|
||||
/**
|
||||
* applySort will add the actual `.sort` and `.skip/.where` clauses to the query
|
||||
* to apply the desired sort.
|
||||
*
|
||||
* @param {Object} ctx the graph context
|
||||
* @param {Object} query the current mongoose query object
|
||||
* @param {Object} params the params from the client describing the query
|
||||
*/
|
||||
const applySort = (ctx, query, {cursor, sort, sortBy}) => {
|
||||
switch (sortBy) {
|
||||
case 'CREATED_AT': {
|
||||
if (cursor) {
|
||||
if (sort === 'DESC') {
|
||||
query = query.where({
|
||||
created_at: {
|
||||
$lt: cursor,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
query = query.where({
|
||||
created_at: {
|
||||
$gt: cursor,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return query.sort({created_at: sort === 'DESC' ? -1 : 1});
|
||||
}
|
||||
case 'REPLIES': {
|
||||
if (cursor) {
|
||||
query = query.skip(cursor);
|
||||
}
|
||||
|
||||
return query.sort({reply_count: sort === 'DESC' ? -1 : 1, created_at: sort === 'DESC' ? -1 : 1});
|
||||
}
|
||||
}
|
||||
|
||||
const SORT_KEY = sortBy.toLowerCase();
|
||||
if (!ctx.plugins || !ctx.plugins.CommentSort || !ctx.plugins.CommentSort[SORT_KEY] || !ctx.plugins.CommentSort[SORT_KEY].sort) {
|
||||
throw new Error(`unable to sort by ${sortBy}, no plugin was provided to handle this type`);
|
||||
}
|
||||
|
||||
return ctx.plugins.CommentSort[SORT_KEY].sort(ctx, query, {cursor, sort});
|
||||
};
|
||||
|
||||
/**
|
||||
* executeWithSort will actually retrieve the comments based on the pre-assembled
|
||||
* query and will compose on top the sort operators necessary to get the desired
|
||||
* result.
|
||||
*
|
||||
* @param {Object} ctx the graph context
|
||||
* @param {Object} query the current mongoose query object
|
||||
* @param {Object} params the params from the client describing the query
|
||||
*/
|
||||
const executeWithSort = async (ctx, query, {cursor, sort, sortBy, limit}) => {
|
||||
|
||||
// Apply the sort to the query.
|
||||
query = applySort(ctx, query, {cursor, sort, sortBy});
|
||||
|
||||
// Apply the limit (if it exists, as it's applied universally).
|
||||
if (limit) {
|
||||
query = query.limit(limit + 1);
|
||||
}
|
||||
|
||||
// Fetch the nodes based on the source query.
|
||||
const nodes = await query.exec();
|
||||
|
||||
// The hasNextPage is always handled the same (ask for one more than we need,
|
||||
// if there is one more, than there is more).
|
||||
let hasNextPage = false;
|
||||
if (limit && nodes.length > limit) {
|
||||
|
||||
// There was one more than we expected! Set hasNextPage = true and remove
|
||||
// the last item from the array that we requested.
|
||||
hasNextPage = true;
|
||||
nodes.splice(limit, 1);
|
||||
}
|
||||
|
||||
// Use the generator functions below to extract the cursor details based on
|
||||
// the current sortBy parameter.
|
||||
return {
|
||||
startCursor: getStartCursor(ctx, nodes, {cursor, sort, sortBy, limit}),
|
||||
endCursor: getEndCursor(ctx, nodes, {cursor, sort, sortBy, limit}),
|
||||
hasNextPage,
|
||||
nodes,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves comments based on the passed in query that is filtered by the
|
||||
* current used passed in via the context.
|
||||
*
|
||||
* @param {Object} context graph context
|
||||
* @param {Object} query query terms to apply to the comments query
|
||||
*/
|
||||
const getCommentsByQuery = async ({user}, {ids, statuses, asset_id, parent_id, author_id, limit, cursor, sort, excludeIgnored, tags, action_type}) => {
|
||||
const getCommentsByQuery = async (ctx, {ids, statuses, asset_id, parent_id, author_id, limit, cursor, sort, sortBy, excludeIgnored, tags, action_type}) => {
|
||||
let comments = CommentModel.find();
|
||||
|
||||
// Only administrators can search for comments with statuses that are not
|
||||
// `null`, or `'ACCEPTED'`.
|
||||
if (user != null && user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS) && statuses && statuses.length > 0) {
|
||||
if (ctx.user != null && ctx.user.can(SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS) && statuses && statuses.length > 0) {
|
||||
comments = comments.where({
|
||||
status: {
|
||||
$in: statuses
|
||||
@@ -158,7 +296,7 @@ const getCommentsByQuery = async ({user}, {ids, statuses, asset_id, parent_id, a
|
||||
});
|
||||
}
|
||||
|
||||
if (user != null && user.can(SEARCH_OTHERS_COMMENTS) && action_type) {
|
||||
if (ctx.user != null && ctx.user.can(SEARCH_OTHERS_COMMENTS) && action_type) {
|
||||
comments = comments.where({
|
||||
action_counts: {
|
||||
[action_type.toLowerCase()]: {
|
||||
@@ -185,7 +323,7 @@ const getCommentsByQuery = async ({user}, {ids, statuses, asset_id, parent_id, a
|
||||
}
|
||||
|
||||
// Only let an admin request any user or the current user request themself.
|
||||
if (user && (user.can(SEARCH_OTHERS_COMMENTS) || user.id === author_id) && author_id != null) {
|
||||
if (ctx.user && (ctx.user.can(SEARCH_OTHERS_COMMENTS) || ctx.user.id === author_id) && author_id != null) {
|
||||
comments = comments.where({author_id});
|
||||
}
|
||||
|
||||
@@ -199,50 +337,19 @@ const getCommentsByQuery = async ({user}, {ids, statuses, asset_id, parent_id, a
|
||||
comments = comments.where({parent_id});
|
||||
}
|
||||
|
||||
if (excludeIgnored && user && user.ignoresUsers && user.ignoresUsers.length > 0) {
|
||||
if (excludeIgnored && ctx.user && ctx.user.ignoresUsers && ctx.user.ignoresUsers.length > 0) {
|
||||
comments = comments.where({
|
||||
author_id: {$nin: user.ignoresUsers}
|
||||
author_id: {$nin: ctx.user.ignoresUsers}
|
||||
});
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
if (sort === 'DESC') {
|
||||
comments = comments.where({
|
||||
created_at: {
|
||||
$lt: cursor
|
||||
}
|
||||
});
|
||||
} else {
|
||||
comments = comments.where({
|
||||
created_at: {
|
||||
$gt: cursor
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let query = comments
|
||||
.sort({created_at: sort === 'DESC' ? -1 : 1});
|
||||
if (limit) {
|
||||
query = query.limit(limit + 1);
|
||||
}
|
||||
return query.then((nodes) => {
|
||||
let hasNextPage = false;
|
||||
if (limit && nodes.length > limit) {
|
||||
hasNextPage = true;
|
||||
nodes.splice(limit, 1);
|
||||
}
|
||||
return Promise.resolve({
|
||||
startCursor: nodes.length ? nodes[0].created_at : null,
|
||||
endCursor: nodes.length ? nodes[nodes.length - 1].created_at : null,
|
||||
hasNextPage,
|
||||
nodes,
|
||||
});
|
||||
});
|
||||
return executeWithSort(ctx, comments, {cursor, sort, sortBy, limit});
|
||||
};
|
||||
|
||||
/**
|
||||
* getComments returns the comments by the id's. Only admins can see non-public comments.
|
||||
* getComments returns the comments by the id's. Only admins can see non-public
|
||||
* comments.
|
||||
*
|
||||
* @param {Object} context graph context
|
||||
* @param {Array<String>} ids the comment id's to fetch
|
||||
* @return {Promise} resolves to the comments
|
||||
@@ -270,6 +377,7 @@ const getComments = ({user}, ids) => {
|
||||
|
||||
/**
|
||||
* Creates a set of loaders based on a GraphQL context.
|
||||
*
|
||||
* @param {Object} context the context of the GraphQL request
|
||||
* @return {Object} object of loaders
|
||||
*/
|
||||
|
||||
@@ -17,10 +17,11 @@ const Asset = {
|
||||
|
||||
return comments.nodes[0];
|
||||
},
|
||||
comments({id}, {query: {sort, limit, excludeIgnored, tags}, deep}, {loaders: {Comments}}) {
|
||||
comments({id}, {query: {sort, sortBy, limit, excludeIgnored, tags}, deep}, {loaders: {Comments}}) {
|
||||
return Comments.getByQuery({
|
||||
asset_id: id,
|
||||
sort,
|
||||
sortBy,
|
||||
limit,
|
||||
parent_id: deep ? undefined : null,
|
||||
tags,
|
||||
|
||||
@@ -11,7 +11,7 @@ const Comment = {
|
||||
user({author_id}, _, {loaders: {Users}}) {
|
||||
return Users.getByID.load(author_id);
|
||||
},
|
||||
replies({id, asset_id, reply_count}, {query: {sort, limit, excludeIgnored}}, {loaders: {Comments}}) {
|
||||
replies({id, asset_id, reply_count}, {query: {sort, sortBy, limit, excludeIgnored}}, {loaders: {Comments}}) {
|
||||
|
||||
// Don't bother looking up replies if there aren't any there!
|
||||
if (reply_count === 0) {
|
||||
@@ -25,6 +25,7 @@ const Comment = {
|
||||
asset_id,
|
||||
parent_id: id,
|
||||
sort,
|
||||
sortBy,
|
||||
limit,
|
||||
excludeIgnored,
|
||||
});
|
||||
|
||||
@@ -249,6 +249,10 @@ input CommentsQuery {
|
||||
# Sort the results by from largest first.
|
||||
sort: SORT_ORDER = DESC
|
||||
|
||||
# The order to sort the comments by, sorting by default the created at
|
||||
# timestamp.
|
||||
sortBy: SORT_COMMENTS_BY = CREATED_AT
|
||||
|
||||
# Filter by a specific tag name.
|
||||
tags: [String!]
|
||||
|
||||
@@ -261,6 +265,10 @@ input RepliesQuery {
|
||||
# Sort the results by from smallest first.
|
||||
sort: SORT_ORDER = ASC
|
||||
|
||||
# The order to sort the comments by, sorting by default the created at
|
||||
# timestamp.
|
||||
sortBy: SORT_COMMENTS_BY = CREATED_AT
|
||||
|
||||
# Limit the number of results to be returned.
|
||||
limit: Int = 3
|
||||
|
||||
@@ -660,6 +668,18 @@ enum SORT_ORDER {
|
||||
ASC
|
||||
}
|
||||
|
||||
# SORT_COMMENTS_BY selects the means for which comments are ordered when
|
||||
# sorting.
|
||||
enum SORT_COMMENTS_BY {
|
||||
|
||||
# Comments will be sorted by their created at date.
|
||||
CREATED_AT
|
||||
|
||||
# Comments will be sorted by their immediate reply count (replies to the comment
|
||||
# in question only, not including descendants).
|
||||
REPLIES
|
||||
}
|
||||
|
||||
# All queries that can be executed.
|
||||
enum USER_STATUS {
|
||||
ACTIVE
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
"passport": "^0.4.0",
|
||||
"passport-jwt": "^3.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"pluralize": "^7.0.0",
|
||||
"postcss-loader": "^1.3.3",
|
||||
"postcss-modules": "^0.5.2",
|
||||
"postcss-smart-import": "^0.5.1",
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
const wrapResponse = require('../../../graph/helpers/response');
|
||||
const {SEARCH_OTHER_USERS} = require('../../../perms/constants');
|
||||
const errors = require('../../../errors');
|
||||
const pluralize = require('pluralize');
|
||||
|
||||
function getReactionConfig(reaction) {
|
||||
reaction = reaction.toLowerCase();
|
||||
|
||||
const reactionPlural = pluralize(reaction);
|
||||
const Reaction = reaction.charAt(0).toUpperCase() + reaction.slice(1);
|
||||
const REACTION = reaction.toUpperCase();
|
||||
const REACTION_PLURAL = reactionPlural.toUpperCase();
|
||||
const typeDefs = `
|
||||
enum ACTION_TYPE {
|
||||
|
||||
@@ -26,6 +29,13 @@ function getReactionConfig(reaction) {
|
||||
item_id: ID!
|
||||
}
|
||||
|
||||
enum SORT_COMMENTS_BY {
|
||||
|
||||
# Comments will be sorted by their count of ${reactionPlural}
|
||||
# on the comment.
|
||||
${REACTION_PLURAL}
|
||||
}
|
||||
|
||||
input Delete${Reaction}ActionInput {
|
||||
|
||||
# The item's id for which we are deleting a ${reaction}.
|
||||
@@ -107,6 +117,27 @@ function getReactionConfig(reaction) {
|
||||
|
||||
return {
|
||||
typeDefs,
|
||||
context: {
|
||||
CommentSort: () => ({
|
||||
[reactionPlural]: {
|
||||
startCursor(ctx, nodes, {cursor}) {
|
||||
|
||||
// The cursor is the start! This is using numeric pagination.
|
||||
return cursor != null ? cursor : 0;
|
||||
},
|
||||
endCursor(ctx, nodes, {cursor}) {
|
||||
return nodes.length ? (cursor != null ? cursor : 0) + nodes.length : null;
|
||||
},
|
||||
sort(ctx, query, {cursor, sort}) {
|
||||
if (cursor) {
|
||||
query = query.skip(cursor);
|
||||
}
|
||||
|
||||
return query.sort({[`action_counts.${reaction}`]: sort === 'DESC' ? -1 : 1, created_at: sort === 'DESC' ? -1 : 1});
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
resolvers: {
|
||||
Subscription: {
|
||||
[`${reaction}ActionCreated`]: ({action}) => {
|
||||
|
||||
@@ -61,7 +61,8 @@ if (WEBPACK) {
|
||||
debug('connection established');
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5648,6 +5648,10 @@ pluralize@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
|
||||
|
||||
pluralize@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
|
||||
|
||||
pop-iterate@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pop-iterate/-/pop-iterate-1.0.1.tgz#ceacfdab4abf353d7a0f2aaa2c1fc7b3f9413ba3"
|
||||
|
||||
Reference in New Issue
Block a user