mirror of
https://github.com/wassname/talk.git
synced 2026-07-20 12:40:47 +08:00
Merge branch 'next' into next-profile
This commit is contained in:
Generated
+13
-3
@@ -1342,9 +1342,9 @@
|
||||
}
|
||||
},
|
||||
"@coralproject/rte": {
|
||||
"version": "0.10.11",
|
||||
"resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-0.10.11.tgz",
|
||||
"integrity": "sha512-Tq8NznCOYx84QpHhZbUldxcYrztEQnnNjDC2aW5HhzltO9nBG2Hu78MzTwliML3MmaTFbLehdIb/BKGKj4eMSw==",
|
||||
"version": "0.10.12",
|
||||
"resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-0.10.12.tgz",
|
||||
"integrity": "sha512-w7UWe6u+TNoPtFcWvyjYkV7eaAE4ccTOYrhdWPsDfM34ZDRq+bM5eiiAMcUVHizvHgsUzFWoN2qjOo+jSfIjCw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bowser": "^1.0.0",
|
||||
@@ -11379,6 +11379,11 @@
|
||||
"source-map-support": "^0.5.1"
|
||||
}
|
||||
},
|
||||
"graphql-fields": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql-fields/-/graphql-fields-1.1.0.tgz",
|
||||
"integrity": "sha1-okZtHEFFVNq4DA93d9a6mg4PdbQ="
|
||||
},
|
||||
"graphql-import": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/graphql-import/-/graphql-import-0.4.5.tgz",
|
||||
@@ -23373,6 +23378,11 @@
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
|
||||
"dev": true
|
||||
},
|
||||
"striptags": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz",
|
||||
"integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0="
|
||||
},
|
||||
"style-loader": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz",
|
||||
|
||||
+3
-1
@@ -48,6 +48,7 @@
|
||||
"fs-extra": "^6.0.1",
|
||||
"graphql": "^0.13.2",
|
||||
"graphql-config": "^2.0.1",
|
||||
"graphql-fields": "^1.1.0",
|
||||
"graphql-playground-middleware-express": "^1.7.2",
|
||||
"graphql-redis-subscriptions": "^1.5.0",
|
||||
"graphql-tools": "^3.0.5",
|
||||
@@ -75,6 +76,7 @@
|
||||
"passport-strategy": "^1.0.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"permit": "^0.2.4",
|
||||
"striptags": "^3.1.1",
|
||||
"subscriptions-transport-ws": "^0.9.12",
|
||||
"tlds": "^1.203.1",
|
||||
"uuid": "^3.3.2"
|
||||
@@ -86,7 +88,7 @@
|
||||
"@babel/polyfill": "7.0.0-beta.49",
|
||||
"@babel/preset-env": "7.0.0-beta.49",
|
||||
"@babel/preset-react": "7.0.0-beta.49",
|
||||
"@coralproject/rte": "^0.10.11",
|
||||
"@coralproject/rte": "^0.10.12",
|
||||
"@types/bcryptjs": "^2.4.1",
|
||||
"@types/bull": "^3.3.16",
|
||||
"@types/bunyan": "^1.8.4",
|
||||
|
||||
@@ -35,14 +35,14 @@ it("renders correctly", () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly when replies are null", () => {
|
||||
it("renders correctly when replies are empty", () => {
|
||||
const props: PropTypesOf<typeof ReplyListContainerN> = {
|
||||
asset: {
|
||||
id: "asset-id",
|
||||
},
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
replies: null,
|
||||
replies: { edges: [] },
|
||||
},
|
||||
relay: {
|
||||
hasMore: noop,
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ exports[`renders correctly 1`] = `
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders correctly when replies are null 1`] = `""`;
|
||||
exports[`renders correctly when replies are empty 1`] = `""`;
|
||||
|
||||
exports[`when has more replies renders hasMore 1`] = `
|
||||
<ReplyList
|
||||
|
||||
@@ -39,6 +39,7 @@ beforeEach(() => {
|
||||
edited: false,
|
||||
editableUntil: "2018-07-06T18:24:30.000Z",
|
||||
},
|
||||
replies: { edges: [], pageInfo: {} },
|
||||
},
|
||||
},
|
||||
clientMutationId: "0",
|
||||
|
||||
@@ -3,15 +3,36 @@ import DataLoader from "dataloader";
|
||||
import Context from "talk-server/graph/tenant/context";
|
||||
import {
|
||||
AssetToCommentsArgs,
|
||||
CommentToParentsArgs,
|
||||
CommentToRepliesArgs,
|
||||
GQLCOMMENT_SORT,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import {
|
||||
Comment,
|
||||
retrieveCommentAssetConnection,
|
||||
retrieveCommentParentsConnection,
|
||||
retrieveCommentRepliesConnection,
|
||||
retrieveCommentUserConnection,
|
||||
retrieveManyComments,
|
||||
} from "talk-server/models/comment";
|
||||
import { Connection } from "talk-server/models/connection";
|
||||
|
||||
/**
|
||||
* primeCommentsFromConnection will prime a given context with the comments
|
||||
* retrieved via a connection.
|
||||
*
|
||||
* @param ctx graph context to use to prime the loaders.
|
||||
*/
|
||||
const primeCommentsFromConnection = (ctx: Context) => (
|
||||
connection: Readonly<Connection<Readonly<Comment>>>
|
||||
) => {
|
||||
// For each of the edges, prime the comment loader.
|
||||
connection.edges.forEach(({ node }) => {
|
||||
ctx.loaders.Comments.comment.prime(node.id, node);
|
||||
});
|
||||
|
||||
return connection;
|
||||
};
|
||||
|
||||
export default (ctx: Context) => ({
|
||||
comment: new DataLoader((ids: string[]) =>
|
||||
@@ -44,7 +65,7 @@ export default (ctx: Context) => ({
|
||||
first,
|
||||
orderBy,
|
||||
after,
|
||||
}),
|
||||
}).then(primeCommentsFromConnection(ctx)),
|
||||
forParent: (
|
||||
assetID: string,
|
||||
parentID: string,
|
||||
@@ -65,5 +86,11 @@ export default (ctx: Context) => ({
|
||||
orderBy,
|
||||
after,
|
||||
}
|
||||
),
|
||||
).then(primeCommentsFromConnection(ctx)),
|
||||
parents: (comment: Comment, { last = 1, before }: CommentToParentsArgs) =>
|
||||
retrieveCommentParentsConnection(ctx.mongo, ctx.tenant.id, comment, {
|
||||
last,
|
||||
// The cursor passed here is always going to be a number.
|
||||
before: before as number,
|
||||
}).then(primeCommentsFromConnection(ctx)),
|
||||
});
|
||||
|
||||
@@ -2,8 +2,17 @@ import DataLoader from "dataloader";
|
||||
import Context from "talk-server/graph/tenant/context";
|
||||
import { retrieveManyUsers, User } from "talk-server/models/user";
|
||||
|
||||
export default (ctx: Context) => ({
|
||||
user: new DataLoader<string, User | null>(ids =>
|
||||
export default (ctx: Context) => {
|
||||
const user = new DataLoader<string, User | null>(ids =>
|
||||
retrieveManyUsers(ctx.mongo, ctx.tenant.id, ids)
|
||||
),
|
||||
});
|
||||
);
|
||||
|
||||
if (ctx.user) {
|
||||
// Prime the current logged in user in the dataloader cache.
|
||||
user.prime(ctx.user.id, ctx.user);
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { GQLCommentTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { getRequestedFields } from "talk-server/graph/tenant/resolvers/util";
|
||||
import {
|
||||
GQLComment,
|
||||
GQLCommentTypeResolver,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { Comment } from "talk-server/models/comment";
|
||||
import { createConnection } from "talk-server/models/connection";
|
||||
|
||||
const Comment: GQLCommentTypeResolver<Comment> = {
|
||||
editing: (comment, input, ctx) => ({
|
||||
@@ -16,7 +21,63 @@ const Comment: GQLCommentTypeResolver<Comment> = {
|
||||
author: (comment, input, ctx) =>
|
||||
ctx.loaders.Users.user.load(comment.author_id),
|
||||
replies: (comment, input, ctx) =>
|
||||
ctx.loaders.Comments.forParent(comment.asset_id, comment.id, input),
|
||||
comment.reply_count > 0
|
||||
? ctx.loaders.Comments.forParent(comment.asset_id, comment.id, input)
|
||||
: createConnection(),
|
||||
parentCount: comment =>
|
||||
comment.parent_id ? comment.grandparent_ids.length + 1 : 0,
|
||||
depth: comment =>
|
||||
comment.parent_id ? comment.grandparent_ids.length + 1 : 0,
|
||||
replyCount: comment => comment.reply_count,
|
||||
rootParent: (comment, input, ctx, info) => {
|
||||
// If there isn't a parent, then return nothing!
|
||||
if (!comment.parent_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// rootParentID is the root parent id for a given comment.
|
||||
const rootParentID =
|
||||
comment.grandparent_ids.length > 0
|
||||
? comment.grandparent_ids[0]
|
||||
: comment.parent_id;
|
||||
|
||||
// Get the field names of the fields being requested, if it's only the ID,
|
||||
// we have that, so no need to make a database request.
|
||||
const fields = getRequestedFields<GQLComment>(info);
|
||||
if (fields.length === 1 && fields[0] === "id") {
|
||||
return {
|
||||
id: rootParentID,
|
||||
};
|
||||
}
|
||||
|
||||
// We want more than the ID! Get the comment!
|
||||
// TODO: (wyattjoh) if the parent and the parents (containing the parent) are requested, the parent comment is retrieved from the database twice. Investigate ways of reducing i/o.
|
||||
return ctx.loaders.Comments.comment.load(rootParentID);
|
||||
},
|
||||
parent: (comment, input, ctx, info) => {
|
||||
// If there isn't a parent, then return nothing!
|
||||
if (!comment.parent_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the field names of the fields being requested, if it's only the ID,
|
||||
// we have that, so no need to make a database request.
|
||||
const fields = getRequestedFields<GQLComment>(info);
|
||||
if (fields.length === 1 && fields[0] === "id") {
|
||||
return {
|
||||
id: comment.parent_id,
|
||||
};
|
||||
}
|
||||
|
||||
// We want more than the ID! Get the comment!
|
||||
// TODO: (wyattjoh) if the parent and the parents (containing the parent) are requested, the parent comment is retrieved from the database twice. Investigate ways of reducing i/o.
|
||||
return ctx.loaders.Comments.comment.load(comment.parent_id);
|
||||
},
|
||||
parents: (comment, input, ctx) =>
|
||||
// Some resolver optimization.
|
||||
comment.parent_id
|
||||
? ctx.loaders.Comments.parents(comment, input)
|
||||
: createConnection(),
|
||||
};
|
||||
|
||||
export default Comment;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { GraphQLResolveInfo } from "graphql";
|
||||
import graphqlFields from "graphql-fields";
|
||||
import { pull } from "lodash";
|
||||
|
||||
/**
|
||||
* getRequestedFields returns the fields in an array that are being queried for.
|
||||
*
|
||||
* @param info query information
|
||||
*/
|
||||
export function getRequestedFields<T>(info: GraphQLResolveInfo) {
|
||||
return pull(Object.keys(graphqlFields<T>(info)), "__typename");
|
||||
}
|
||||
@@ -289,6 +289,27 @@ type Karma {
|
||||
thresholds: KarmaThresholds!
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## CharCount
|
||||
################################################################################
|
||||
|
||||
type CharCount {
|
||||
"""
|
||||
enabled when true, enables the character count moderation phase.
|
||||
"""
|
||||
enabled: Boolean!
|
||||
|
||||
"""
|
||||
min is the smallest length of a Comment that may be posted.
|
||||
"""
|
||||
min: Int
|
||||
|
||||
"""
|
||||
max is the largest length of a Comment that may be posted.
|
||||
"""
|
||||
max: Int
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Email
|
||||
################################################################################
|
||||
@@ -411,14 +432,9 @@ type Settings {
|
||||
editCommentWindowLength: Int!
|
||||
|
||||
"""
|
||||
charCountEnable is true when the character count restriction is enabled.
|
||||
charCount stores the character count moderation settings.
|
||||
"""
|
||||
charCountEnable: Boolean!
|
||||
|
||||
"""
|
||||
charCount is the maximum number of characters a comment may be.
|
||||
"""
|
||||
charCount: Int
|
||||
charCount: CharCount!
|
||||
|
||||
"""
|
||||
organizationName is the name of the organization.
|
||||
@@ -633,11 +649,22 @@ type Comment {
|
||||
"""
|
||||
status: COMMENT_STATUS!
|
||||
|
||||
"""
|
||||
parentCount is the number of direct parents for this Comment. Currently this
|
||||
value is the same as depth.
|
||||
"""
|
||||
parentCount: Int!
|
||||
|
||||
"""
|
||||
depth is the number of levels that a given comment is deep.
|
||||
"""
|
||||
depth: Int!
|
||||
|
||||
"""
|
||||
replyCount is the number of replies. Only direct replies to this Comment
|
||||
are counted. Deleted comments are included in this count.
|
||||
"""
|
||||
replyCount: Int
|
||||
replyCount: Int!
|
||||
|
||||
"""
|
||||
replies will return the replies to this Comment.
|
||||
@@ -646,7 +673,24 @@ type Comment {
|
||||
first: Int = 10
|
||||
orderBy: COMMENT_SORT = CREATED_AT_DESC
|
||||
after: Cursor
|
||||
): CommentsConnection
|
||||
): CommentsConnection!
|
||||
|
||||
"""
|
||||
parent is the immediate parent of a given comment.
|
||||
"""
|
||||
parent: Comment
|
||||
|
||||
"""
|
||||
rootParent is the highest level parent Comment. This Comment would have been
|
||||
left on the Asset itself.
|
||||
"""
|
||||
rootParent: Comment
|
||||
|
||||
"""
|
||||
parents returns a CommentsConnection that allows accessing direct parents of
|
||||
the given Comment.
|
||||
"""
|
||||
parents(last: Int = 1, before: Cursor): CommentsConnection!
|
||||
|
||||
"""
|
||||
editing returns details about the edit status of a Comment.
|
||||
@@ -806,11 +850,6 @@ type Query {
|
||||
"""
|
||||
comment(id: ID!): Comment
|
||||
|
||||
"""
|
||||
assets returns a AssetsConnection.
|
||||
"""
|
||||
assets(cursor: Cursor, limit: Int = 10): AssetsConnection
|
||||
|
||||
"""
|
||||
asset is the Asset specified by its ID/URL.
|
||||
"""
|
||||
@@ -1113,6 +1152,23 @@ input SettingsKarmaInput {
|
||||
thresholds: SettingsKarmaThresholdsInput
|
||||
}
|
||||
|
||||
input SettingsCharCountInput {
|
||||
"""
|
||||
enabled when true, enables the character count moderation phase.
|
||||
"""
|
||||
enabled: Boolean
|
||||
|
||||
"""
|
||||
min is the smallest length of a Comment that may be posted.
|
||||
"""
|
||||
min: Int
|
||||
|
||||
"""
|
||||
max is the largest length of a Comment that may be posted.
|
||||
"""
|
||||
max: Int
|
||||
}
|
||||
|
||||
"""
|
||||
SettingsInput is the partial type of the Settings type for performing mutations.
|
||||
"""
|
||||
@@ -1204,16 +1260,6 @@ input SettingsInput {
|
||||
"""
|
||||
editCommentWindowLength: Int
|
||||
|
||||
"""
|
||||
charCountEnable is true when the character count restriction is enabled.
|
||||
"""
|
||||
charCountEnable: Boolean
|
||||
|
||||
"""
|
||||
charCount is the maximum number of characters a comment may be.
|
||||
"""
|
||||
charCount: Int
|
||||
|
||||
"""
|
||||
organizationName is the name of the organization.
|
||||
"""
|
||||
@@ -1249,6 +1295,11 @@ input SettingsInput {
|
||||
handled.
|
||||
"""
|
||||
karma: SettingsKarmaInput
|
||||
|
||||
"""
|
||||
charCount stores the character count moderation settings.
|
||||
"""
|
||||
charCount: SettingsCharCountInput
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { ActionCounts } from "talk-server/models/actions";
|
||||
import {
|
||||
Connection,
|
||||
createConnection,
|
||||
Cursor,
|
||||
getPageInfo,
|
||||
nodesToEdges,
|
||||
@@ -28,7 +29,7 @@ export interface BodyHistoryItem {
|
||||
}
|
||||
|
||||
export interface StatusHistoryItem {
|
||||
status: GQLCOMMENT_STATUS; // TODO: migrate field
|
||||
status: GQLCOMMENT_STATUS;
|
||||
assigned_by?: string;
|
||||
created_at: Date;
|
||||
}
|
||||
@@ -43,6 +44,8 @@ export interface Comment extends TenantResource {
|
||||
status: GQLCOMMENT_STATUS;
|
||||
status_history: StatusHistoryItem[];
|
||||
action_counts: ActionCounts;
|
||||
grandparent_ids: string[];
|
||||
reply_ids: string[];
|
||||
reply_count: number;
|
||||
created_at: Date;
|
||||
deleted_at?: Date;
|
||||
@@ -54,6 +57,7 @@ export type CreateCommentInput = Omit<
|
||||
| "id"
|
||||
| "tenant_id"
|
||||
| "created_at"
|
||||
| "reply_ids"
|
||||
| "reply_count"
|
||||
| "body_history"
|
||||
| "status_history"
|
||||
@@ -75,6 +79,7 @@ export async function createComment(
|
||||
id: uuid.v4(),
|
||||
tenant_id: tenantID,
|
||||
created_at: now,
|
||||
reply_ids: [],
|
||||
reply_count: 0,
|
||||
body_history: [
|
||||
{
|
||||
@@ -102,6 +107,31 @@ export async function createComment(
|
||||
return comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* pushChildCommentIDOntoParent will push the new child comment's ID onto the
|
||||
* parent comment so it can reference direct children.
|
||||
*/
|
||||
export async function pushChildCommentIDOntoParent(
|
||||
mongo: Db,
|
||||
tenantID: string,
|
||||
parentID: string,
|
||||
childID: string
|
||||
) {
|
||||
// This pushes the new child ID onto the parent comment.
|
||||
const result = await collection(mongo).findOneAndUpdate(
|
||||
{
|
||||
tenant_id: tenantID,
|
||||
id: parentID,
|
||||
},
|
||||
{
|
||||
$push: { reply_ids: childID },
|
||||
$inc: { reply_count: 1 },
|
||||
}
|
||||
);
|
||||
|
||||
return result.value;
|
||||
}
|
||||
|
||||
export type EditCommentInput = Pick<
|
||||
Comment,
|
||||
"id" | "author_id" | "body" | "status" | "metadata"
|
||||
@@ -273,6 +303,100 @@ export async function retrieveCommentRepliesConnection(
|
||||
return retrieveConnection(input, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieveCommentParentsConnection will return a comment connection used to
|
||||
* represent the parents of a given comment.
|
||||
*
|
||||
* @param mongo the database connection to use when retrieving comments
|
||||
* @param tenantID the tenant id for where the comment exists
|
||||
* @param commentID the id of the comment to retrieve parents of
|
||||
* @param pagination pagination options to paginate the results
|
||||
*/
|
||||
export async function retrieveCommentParentsConnection(
|
||||
mongo: Db,
|
||||
tenantID: string,
|
||||
comment: Comment,
|
||||
{ last: limit, before: skip = -1 }: { last: number; before?: number }
|
||||
): Promise<Readonly<Connection<Readonly<Comment>>>> {
|
||||
// Return nothing if this comment does not have any parents.
|
||||
if (!comment.parent_id) {
|
||||
return createConnection({
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: (wyattjoh) maybe throw an error when the limit is zero?
|
||||
|
||||
if (limit <= 0) {
|
||||
return createConnection({
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// If the last paramter is 1, and the after paramter is either unset or equal
|
||||
// to zero, then all we have to return is the direct parent.
|
||||
if (limit === 1 && skip < 0) {
|
||||
const parent = await retrieveComment(mongo, tenantID, comment.parent_id);
|
||||
if (!parent) {
|
||||
throw new Error("parent comment not found");
|
||||
}
|
||||
|
||||
return {
|
||||
edges: [{ node: parent, cursor: 0 }],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: comment.grandparent_ids.length > 0,
|
||||
endCursor: 0,
|
||||
startCursor: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Create a list of all the comment parent ids, in reverse order.
|
||||
const parentIDs = [comment.parent_id, ...comment.grandparent_ids.reverse()];
|
||||
|
||||
// Fetch the subset of the comment id's that we are going to query for.
|
||||
const parentIDSubset = parentIDs.slice(skip + 1, skip + 1 + limit);
|
||||
|
||||
// Retrieve the parents via the subset list.
|
||||
const parents = await retrieveManyComments(mongo, tenantID, parentIDSubset);
|
||||
|
||||
// Loop over the list to ensure that none of the entries is null (indicating
|
||||
// that there was a misplaced parent). We can assert the type here because we
|
||||
// will throw an error and abort if one of the comments are null.
|
||||
parents.forEach(parentComment => {
|
||||
if (!parentComment) {
|
||||
// TODO: (wyattjoh) replace with a better error.
|
||||
throw new Error("parent id specified does not exist");
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const edges = nodesToEdges(
|
||||
// We can't have a null parent after the forEach filter above.
|
||||
parents as Array<Readonly<Comment>>,
|
||||
(_, index) => index + skip + 1
|
||||
).reverse();
|
||||
|
||||
// Return the resolved connection.
|
||||
return {
|
||||
edges,
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: parentIDs.length > limit + skip,
|
||||
startCursor: edges.length > 0 ? edges[0].cursor : null,
|
||||
endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieveAssetConnection returns a Connection<Comment> for a given Asset's
|
||||
* comments.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { merge } from "lodash";
|
||||
|
||||
export type Cursor = Date | number | string | null;
|
||||
|
||||
export interface Edge<T> {
|
||||
@@ -17,6 +19,25 @@ export interface Connection<T> {
|
||||
pageInfo: PageInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* createConnection will create a base Connection that can be used to satisfy
|
||||
* the Connection<T> interface.
|
||||
*
|
||||
* @param connection the base connection to optionally merge with the default base
|
||||
* connection details.
|
||||
*/
|
||||
export function createConnection<T>(
|
||||
connection: Partial<Connection<T>> = {}
|
||||
): Connection<T> {
|
||||
return merge(
|
||||
{
|
||||
edges: [],
|
||||
pageInfo: {},
|
||||
},
|
||||
connection
|
||||
);
|
||||
}
|
||||
|
||||
export interface PaginationArgs {
|
||||
first: number;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
GQLAuth,
|
||||
GQLCharCount,
|
||||
GQLEmail,
|
||||
GQLExternalIntegrations,
|
||||
GQLKarma,
|
||||
@@ -62,8 +63,7 @@ export interface ModerationSettings {
|
||||
closedMessage?: string;
|
||||
disableCommenting: boolean;
|
||||
disableCommentingMessage?: string;
|
||||
charCountEnable: boolean;
|
||||
charCount?: number;
|
||||
charCount: GQLCharCount;
|
||||
}
|
||||
|
||||
export interface Settings extends ModerationSettings {
|
||||
|
||||
@@ -71,7 +71,9 @@ export async function createTenant(db: Db, input: CreateTenantInput) {
|
||||
closedTimeout: 60 * 60 * 24 * 7 * 2,
|
||||
disableCommenting: false,
|
||||
editCommentWindowLength: 30 * 1000,
|
||||
charCountEnable: false,
|
||||
charCount: {
|
||||
enabled: false,
|
||||
},
|
||||
wordlist: {
|
||||
suspect: [],
|
||||
banned: [],
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
CreateCommentInput,
|
||||
editComment,
|
||||
EditCommentInput,
|
||||
pushChildCommentIDOntoParent,
|
||||
retrieveComment,
|
||||
} from "talk-server/models/comment";
|
||||
import { Tenant } from "talk-server/models/tenant";
|
||||
@@ -16,7 +17,7 @@ import { Request } from "talk-server/types/express";
|
||||
|
||||
export type CreateComment = Omit<
|
||||
CreateCommentInput,
|
||||
"status" | "action_counts" | "metadata"
|
||||
"status" | "action_counts" | "metadata" | "grandparent_ids"
|
||||
>;
|
||||
|
||||
export async function create(
|
||||
@@ -35,6 +36,7 @@ export async function create(
|
||||
|
||||
// TODO: (wyattjoh) Check that the asset was visible.
|
||||
|
||||
const grandparentIDs: string[] = [];
|
||||
if (input.parent_id) {
|
||||
// Check to see that the reference parent ID exists.
|
||||
const parent = await retrieveComment(mongo, tenant.id, input.parent_id);
|
||||
@@ -44,6 +46,13 @@ export async function create(
|
||||
}
|
||||
|
||||
// TODO: (wyattjoh) Check that the parent comment was visible.
|
||||
|
||||
// Push the parent's parent id's into the comment's grandparent id's.
|
||||
grandparentIDs.push(...parent.grandparent_ids);
|
||||
if (parent.parent_id) {
|
||||
// If this parent has a parent, push it down as well.
|
||||
grandparentIDs.push(parent.parent_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the comment through the moderation phases.
|
||||
@@ -61,11 +70,18 @@ export async function create(
|
||||
...input,
|
||||
status,
|
||||
action_counts: {},
|
||||
grandparent_ids: grandparentIDs,
|
||||
metadata,
|
||||
});
|
||||
|
||||
if (input.parent_id) {
|
||||
// TODO: update reply count of parent.
|
||||
// Push the child's ID onto the parent.
|
||||
await pushChildCommentIDOntoParent(
|
||||
mongo,
|
||||
tenant.id,
|
||||
input.parent_id,
|
||||
comment.id
|
||||
);
|
||||
}
|
||||
|
||||
return comment;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import striptags from "striptags";
|
||||
|
||||
import { isNil } from "lodash";
|
||||
import {
|
||||
GQLACTION_GROUP,
|
||||
GQLACTION_TYPE,
|
||||
@@ -9,28 +12,40 @@ import {
|
||||
IntermediatePhaseResult,
|
||||
} from "talk-server/services/comments/moderation";
|
||||
|
||||
const testCharCount = (settings: Partial<ModerationSettings>, length: number) =>
|
||||
settings.charCountEnable && settings.charCount && length > settings.charCount;
|
||||
const testCharCount = (
|
||||
settings: Partial<ModerationSettings>,
|
||||
length: number
|
||||
) => {
|
||||
// settings.charCount.enable && settings.charCount && length > settings.charCount;
|
||||
|
||||
if (settings.charCount && settings.charCount.enabled) {
|
||||
if (!isNil(settings.charCount.min)) {
|
||||
if (length < settings.charCount.min) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!isNil(settings.charCount.max)) {
|
||||
if (length > settings.charCount.max) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const commentLength: IntermediateModerationPhase = ({
|
||||
asset,
|
||||
tenant,
|
||||
comment,
|
||||
}): IntermediatePhaseResult | void => {
|
||||
const length = comment.body ? comment.body.length : 0;
|
||||
const length = comment.body ? striptags(comment.body).length : 0;
|
||||
|
||||
// Check to see if the body is too short, if it is, then complain about it!
|
||||
if (length < 2) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("comment body too short");
|
||||
}
|
||||
|
||||
// Reject if the comment is too long
|
||||
// Reject if the comment is too long or too short.
|
||||
if (
|
||||
testCharCount(tenant, length) ||
|
||||
(asset.settings && testCharCount(asset.settings, length))
|
||||
) {
|
||||
// Add the flag related to Trust to the comment.
|
||||
return {
|
||||
status: GQLCOMMENT_STATUS.REJECTED,
|
||||
actions: [
|
||||
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
declare module "graphql-fields" {
|
||||
import { GraphQLResolveInfo } from "graphql";
|
||||
|
||||
export default function graphqlFields<T>(
|
||||
info: GraphQLResolveInfo
|
||||
): { [P in keyof T]: any };
|
||||
}
|
||||
Reference in New Issue
Block a user