mirror of
https://github.com/wassname/talk.git
synced 2026-09-09 11:38:08 +08:00
feat: added action counts to graph
This commit is contained in:
Generated
+11
-13
@@ -1647,9 +1647,9 @@
|
||||
}
|
||||
},
|
||||
"@types/bson": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-1.0.10.tgz",
|
||||
"integrity": "sha512-gRf+Qy5Qiyjz28ZkPRP37bDHtGG67op/lV2qcIMhWUq4vIMJ6/j13ajeYH7LFhJ5RNflyLHmdANPGDXZ5a8EzQ==",
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/bson/-/bson-1.0.11.tgz",
|
||||
"integrity": "sha512-j+UcCWI+FsbI5/FQP/Kj2CXyplWAz39ktHFkXk84h7dNblKRSoNJs95PZFRd96NQGqsPEPgeclqnznWZr14ZDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
@@ -1989,9 +1989,9 @@
|
||||
}
|
||||
},
|
||||
"@types/mongodb": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.1.1.tgz",
|
||||
"integrity": "sha512-lHEH+OwYNeuC28jlmdPT/wBAVMuB6M1sHjZKAtaho/LeJf78ILJPYUq2OD7mWVj9QR7uYiKVt4ExGkXegHFCJQ==",
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.1.7.tgz",
|
||||
"integrity": "sha512-ljS4mE9o3apEkI59pftdnLf3b1ZczMPtXWp1myrhR+E/CLk0O5SjbTt6Rn3OxMB+Qc2eAytrQVufa4y1pFqE2A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/bson": "*",
|
||||
@@ -10222,8 +10222,11 @@
|
||||
"resolved": "https://registry.npmjs.org/fluent-intl-polyfill/-/fluent-intl-polyfill-0.1.0.tgz",
|
||||
"integrity": "sha1-ETOUSrJHeINHOZVZaIPg05z4hc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"intl-pluralrules": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b"
|
||||
"dependencies": {
|
||||
"intl-pluralrules": {
|
||||
"version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b",
|
||||
"from": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fluent-langneg": {
|
||||
@@ -12667,11 +12670,6 @@
|
||||
"integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=",
|
||||
"dev": true
|
||||
},
|
||||
"intl-pluralrules": {
|
||||
"version": "github:projectfluent/IntlPluralRules#94cb0fa1c23ad943bc5aafef43cea132fa51d68b",
|
||||
"from": "github:projectfluent/IntlPluralRules#module",
|
||||
"dev": true
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@
|
||||
"@types/lodash": "^4.14.111",
|
||||
"@types/luxon": "^0.5.3",
|
||||
"@types/mini-css-extract-plugin": "^0.2.0",
|
||||
"@types/mongodb": "^3.1.1",
|
||||
"@types/mongodb": "^3.1.7",
|
||||
"@types/ms": "^0.7.30",
|
||||
"@types/node": "^10.5.2",
|
||||
"@types/node-fetch": "^2.1.2",
|
||||
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
CommentToRepliesArgs,
|
||||
GQLCOMMENT_SORT,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import {
|
||||
ACTION_ITEM_TYPE,
|
||||
ActionCounts,
|
||||
retrieveManyAuthoredActionCounts,
|
||||
} from "talk-server/models/actions";
|
||||
import {
|
||||
retrieveCommentAssetConnection,
|
||||
retrieveCommentRepliesConnection,
|
||||
@@ -16,6 +21,17 @@ export default (ctx: Context) => ({
|
||||
comment: new DataLoader((ids: string[]) =>
|
||||
retrieveManyComments(ctx.mongo, ctx.tenant.id, ids)
|
||||
),
|
||||
retrieveAuthoredActionCounts: new DataLoader<string, ActionCounts>(
|
||||
(itemIDs: string[]) =>
|
||||
retrieveManyAuthoredActionCounts(
|
||||
ctx.mongo,
|
||||
ctx.tenant.id,
|
||||
// This should only ever be accessed when a user is logged in.
|
||||
ctx.user!.id,
|
||||
ACTION_ITEM_TYPE.COMMENTS,
|
||||
itemIDs
|
||||
)
|
||||
),
|
||||
forAsset: (
|
||||
assetID: string,
|
||||
// Apply the graph schema defaults at the loader.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GQLCommentTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { decodeActionCounts } from "talk-server/models/actions";
|
||||
import { Comment } from "talk-server/models/comment";
|
||||
|
||||
const Comment: GQLCommentTypeResolver<Comment> = {
|
||||
@@ -17,6 +18,9 @@ const Comment: GQLCommentTypeResolver<Comment> = {
|
||||
ctx.loaders.Users.user.load(comment.author_id),
|
||||
replies: (comment, input, ctx) =>
|
||||
ctx.loaders.Comments.forParent(comment.asset_id, comment.id, input),
|
||||
actionCounts: comment => decodeActionCounts(comment.action_counts),
|
||||
authoredActionCounts: (comment, input, ctx) =>
|
||||
ctx.loaders.Comments.retrieveAuthoredActionCounts.load(comment.id),
|
||||
};
|
||||
|
||||
export default Comment;
|
||||
|
||||
@@ -210,6 +210,37 @@ type CommentDontAgreeAction implements CommentAction {
|
||||
createdAt: Time!
|
||||
}
|
||||
|
||||
type ReactionActionCounts {
|
||||
total: Int!
|
||||
}
|
||||
|
||||
type DontAgreeActionCounts {
|
||||
total: Int!
|
||||
}
|
||||
|
||||
type FlagReasonActionCounts {
|
||||
COMMENT_REPORTED_OFFENSIVE: Int!
|
||||
COMMENT_REPORTED_SPAM: Int!
|
||||
COMMENT_DETECTED_TOXIC: Int!
|
||||
COMMENT_DETECTED_SPAM: Int!
|
||||
COMMENT_DETECTED_BODY_COUNT: Int!
|
||||
COMMENT_DETECTED_TRUST: Int!
|
||||
COMMENT_DETECTED_LINKS: Int!
|
||||
COMMENT_DETECTED_BANNED_WORD: Int!
|
||||
COMMENT_DETECTED_SUSPECT_WORD: Int!
|
||||
}
|
||||
|
||||
type FlagActionCounts {
|
||||
total: Int!
|
||||
reasons: FlagReasonActionCounts!
|
||||
}
|
||||
|
||||
type ActionCounts {
|
||||
reaction: ReactionActionCounts!
|
||||
dontagree: DontAgreeActionCounts!
|
||||
flag: FlagActionCounts!
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Settings
|
||||
################################################################################
|
||||
@@ -829,6 +860,17 @@ type Comment {
|
||||
editing returns details about the edit status of a Comment.
|
||||
"""
|
||||
editing: EditInfo!
|
||||
|
||||
"""
|
||||
actionCounts stores the counts of all the actions for the Comment.
|
||||
"""
|
||||
actionCounts: ActionCounts!
|
||||
|
||||
"""
|
||||
authoredActionCounts stores the counts of all the actions for the Comment as
|
||||
it is written by the current User.
|
||||
"""
|
||||
authoredActionCounts: ActionCounts! @auth
|
||||
}
|
||||
|
||||
type PageInfo {
|
||||
@@ -996,7 +1038,7 @@ type Query {
|
||||
"""
|
||||
me is the current logged in User.
|
||||
"""
|
||||
me: User
|
||||
me: User @auth
|
||||
|
||||
"""
|
||||
settings is the Settings for a given Tenant.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
GQLCOMMENT_FLAG_REASON,
|
||||
GQLFlagReasonActionCounts,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
|
||||
type ExtractKeys<T> = { [P in keyof T]: P }[keyof T];
|
||||
type A = ExtractKeys<typeof GQLCOMMENT_FLAG_REASON>;
|
||||
type B = ExtractKeys<GQLFlagReasonActionCounts>;
|
||||
|
||||
// These tests ensure that the enums contained in GQLCOMMENT_FLAG_REASON are
|
||||
// also defined on the GQLFlagReasonActionCounts type.
|
||||
describe("GQLFlagReasonActionCounts", () => {
|
||||
it("contains all the flag enum types", () => {
|
||||
const a: A = "" as any;
|
||||
let b: B = "" as any;
|
||||
b = a;
|
||||
expect(b).toBe("");
|
||||
|
||||
let c: A = "" as any;
|
||||
const d: B = "" as any;
|
||||
c = d;
|
||||
expect(c).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -193,6 +193,40 @@ export async function createActions(
|
||||
return Promise.all(inputs.map(input => createAction(mongo, tenantID, input)));
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieveManyAuthoredActionCounts returns the action counts for a specific
|
||||
* user.
|
||||
*/
|
||||
export async function retrieveManyAuthoredActionCounts(
|
||||
mongo: Db,
|
||||
tenantID: string,
|
||||
userID: string | null,
|
||||
itemType: ACTION_ITEM_TYPE,
|
||||
itemIDs: string[]
|
||||
) {
|
||||
const cursor = await collection(mongo).find({
|
||||
tenant_id: tenantID,
|
||||
user_id: userID,
|
||||
item_type: itemType,
|
||||
item_id: { $in: itemIDs },
|
||||
});
|
||||
|
||||
const actions = await cursor.toArray();
|
||||
|
||||
// For each of the actions returned by the query, group the actions by the
|
||||
// item id. Then compute the action counts for each of these action groups,
|
||||
// which can be used to determine existence of actions.
|
||||
return itemIDs
|
||||
.map(itemID => actions.filter(action => action.item_id === itemID))
|
||||
.map(itemActions =>
|
||||
itemActions.reduce(
|
||||
(actionCounts, { action_type, reason }) =>
|
||||
incrementActionCounts(actionCounts, action_type, reason),
|
||||
createEmptyActionCounts()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export type DeleteActionInput = Pick<
|
||||
Action,
|
||||
"action_type" | "item_type" | "item_id" | "reason" | "user_id"
|
||||
@@ -349,16 +383,10 @@ function decodeActionCountKey(key: string): DecodedActionCountKey {
|
||||
}
|
||||
|
||||
/**
|
||||
* decodeActionCounts will take the encoded action counts and decode them into
|
||||
* a useable format.
|
||||
*
|
||||
* @param encodedActionCounts the action counts to decode
|
||||
* createEmptyActionCounts creates a default/empty set of action counts.
|
||||
*/
|
||||
export function decodeActionCounts(
|
||||
encodedActionCounts: EncodedActionCounts
|
||||
): ActionCounts {
|
||||
// Default all the action counts to zero.
|
||||
const actionCounts: ActionCounts = {
|
||||
function createEmptyActionCounts(): ActionCounts {
|
||||
return {
|
||||
[ACTION_TYPE.REACTION]: {
|
||||
total: 0,
|
||||
},
|
||||
@@ -376,35 +404,59 @@ export function decodeActionCounts(
|
||||
) as Record<GQLCOMMENT_FLAG_REASON, number>,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* decodeActionCounts will take the encoded action counts and decode them into
|
||||
* a useable format.
|
||||
*
|
||||
* @param encodedActionCounts the action counts to decode
|
||||
*/
|
||||
export function decodeActionCounts(
|
||||
encodedActionCounts: EncodedActionCounts
|
||||
): ActionCounts {
|
||||
// Default all the action counts to zero.
|
||||
const actionCounts: ActionCounts = createEmptyActionCounts();
|
||||
|
||||
// Loop over all the encoded action counts to extract each of the action
|
||||
// counts as they are encoded.
|
||||
Object.entries(encodedActionCounts).forEach(([key, count]) => {
|
||||
Object.entries(encodedActionCounts).map(([key, count]) => {
|
||||
// Pull out the action type and the reason from the key.
|
||||
const { actionType, reason } = decodeActionCountKey(key);
|
||||
|
||||
// Handle the different types and reasons.
|
||||
switch (actionType) {
|
||||
case ACTION_TYPE.REACTION:
|
||||
actionCounts[ACTION_TYPE.REACTION].total += count;
|
||||
break;
|
||||
case ACTION_TYPE.DONT_AGREE:
|
||||
actionCounts[ACTION_TYPE.DONT_AGREE].total += count;
|
||||
break;
|
||||
case ACTION_TYPE.FLAG:
|
||||
// When we have a reason, we are incrementing for that particular reason
|
||||
// rather than incrementing for the total. If we don't have a reason, we
|
||||
// just got the updated reason.
|
||||
if (reason) {
|
||||
actionCounts[ACTION_TYPE.FLAG].reasons[reason] += count;
|
||||
} else {
|
||||
actionCounts[ACTION_TYPE.FLAG].total += count;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("unexpected action type");
|
||||
}
|
||||
incrementActionCounts(actionCounts, actionType, reason, count);
|
||||
});
|
||||
|
||||
return actionCounts;
|
||||
}
|
||||
|
||||
function incrementActionCounts(
|
||||
actionCounts: ActionCounts,
|
||||
actionType: ACTION_TYPE,
|
||||
reason: GQLCOMMENT_FLAG_REASON | undefined,
|
||||
count: number = 1
|
||||
) {
|
||||
switch (actionType) {
|
||||
case ACTION_TYPE.REACTION:
|
||||
actionCounts[ACTION_TYPE.REACTION].total += count;
|
||||
break;
|
||||
case ACTION_TYPE.DONT_AGREE:
|
||||
actionCounts[ACTION_TYPE.DONT_AGREE].total += count;
|
||||
break;
|
||||
case ACTION_TYPE.FLAG:
|
||||
// When we have a reason, we are incrementing for that particular reason
|
||||
// rather than incrementing for the total. If we don't have a reason, we
|
||||
// just got the updated reason.
|
||||
if (reason) {
|
||||
actionCounts[ACTION_TYPE.FLAG].reasons[reason] += count;
|
||||
} else {
|
||||
actionCounts[ACTION_TYPE.FLAG].total += count;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("unexpected action type");
|
||||
}
|
||||
|
||||
return actionCounts;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user