mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-22 19:01:32 +00:00
First pass at adding comment trees. (#2362)
* First pass at adding comment trees. - Extracted comment replies into its own table. - Added ltree column to comment - Added parent_id param to GetComments to fetch a tree branch - No paging / limiting yet * Adding child_count to comment_aggregates. * Adding parent comment update counts * Fix unit tests. * Comment tree paging mostly done. * Fix clippy * Fix drone tests wrong postgres version. * Fix unit tests. * Add back in delete in unit test. * Add postgres upgrade script. * Fixing some PR comments. * Move update ltree into Comment::create * Updating based on comments. * Fix send soft fail.
This commit is contained in:
parent
becb8b4f66
commit
9c3efe32e7
72 changed files with 3692 additions and 2661 deletions
|
@ -155,7 +155,7 @@ steps:
|
|||
|
||||
services:
|
||||
- name: database
|
||||
image: postgres:12-alpine
|
||||
image: postgres:14-alpine
|
||||
environment:
|
||||
POSTGRES_USER: lemmy
|
||||
POSTGRES_PASSWORD: password
|
||||
|
|
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -834,12 +834,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.1"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
dependencies = [
|
||||
"darling_core 0.13.1",
|
||||
"darling_macro 0.13.1",
|
||||
"darling_core 0.13.4",
|
||||
"darling_macro 0.13.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -868,9 +868,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.1"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324"
|
||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
|
@ -907,11 +907,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.1"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
dependencies = [
|
||||
"darling_core 0.13.1",
|
||||
"darling_core 0.13.4",
|
||||
"quote 1.0.18",
|
||||
"syn 1.0.96",
|
||||
]
|
||||
|
@ -1058,6 +1058,16 @@ dependencies = [
|
|||
"syn 1.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_ltree"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55a0b2b2e948a2d8ab673ccee9f37b20bdcc8b7acb40a242a0fdf53d4c2678b0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"diesel",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_migrations"
|
||||
version = "1.4.0"
|
||||
|
@ -1132,7 +1142,7 @@ name = "doku-derive"
|
|||
version = "0.11.0"
|
||||
source = "git+https://github.com/anixe/doku#10a0339a82be92b5f160aac325d11c9c2ef875e1"
|
||||
dependencies = [
|
||||
"darling 0.13.1",
|
||||
"darling 0.13.4",
|
||||
"proc-macro2 1.0.39",
|
||||
"quote 1.0.18",
|
||||
"syn 1.0.96",
|
||||
|
@ -1984,6 +1994,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"diesel",
|
||||
"diesel-derive-newtype",
|
||||
"diesel_ltree",
|
||||
"diesel_migrations",
|
||||
"lemmy_utils",
|
||||
"once_cell",
|
||||
|
@ -2002,6 +2013,7 @@ name = "lemmy_db_views"
|
|||
version = "0.16.5"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
"diesel_ltree",
|
||||
"lemmy_db_schema",
|
||||
"serde",
|
||||
"serial_test",
|
||||
|
@ -3507,22 +3519,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "1.12.0"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec1e6ec4d8950e5b1e894eac0d360742f3b1407a6078a604a731c4b3f49cefbc"
|
||||
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_with_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "1.5.1"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e"
|
||||
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
||||
dependencies = [
|
||||
"darling 0.13.1",
|
||||
"darling 0.13.4",
|
||||
"proc-macro2 1.0.39",
|
||||
"quote 1.0.18",
|
||||
"syn 1.0.96",
|
||||
|
|
|
@ -12,14 +12,17 @@
|
|||
"api-test": "jest -i follow.spec.ts && jest -i src/post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sniptt/monads": "^0.5.10",
|
||||
"@types/jest": "^26.0.23",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-plugin-jane": "^9.0.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-plugin-jane": "^11.2.2",
|
||||
"jest": "^27.0.6",
|
||||
"lemmy-js-client": "0.17.0-rc.11",
|
||||
"lemmy-js-client": "0.17.0-rc.37",
|
||||
"node-fetch": "^2.6.1",
|
||||
"prettier": "^2.3.2",
|
||||
"prettier": "^2.7.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"ts-jest": "^27.0.3",
|
||||
"typescript": "^4.3.5"
|
||||
"typescript": "^4.6.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
jest.setTimeout(180000);
|
||||
import {None, Some} from '@sniptt/monads';
|
||||
import { CommentView } from 'lemmy-js-client';
|
||||
import { PostResponse } from 'lemmy-js-client';
|
||||
|
||||
import {
|
||||
alpha,
|
||||
beta,
|
||||
|
@ -24,10 +28,9 @@ import {
|
|||
randomString,
|
||||
API,
|
||||
unfollows,
|
||||
getComments,
|
||||
getCommentParentId,
|
||||
} from './shared';
|
||||
import { CommentView } from 'lemmy-js-client';
|
||||
|
||||
import { PostResponse } from 'lemmy-js-client';
|
||||
|
||||
let postRes: PostResponse;
|
||||
|
||||
|
@ -39,7 +42,7 @@ beforeAll(async () => {
|
|||
let betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
||||
postRes = await createPost(
|
||||
alpha,
|
||||
betaCommunity.community.id
|
||||
betaCommunity.unwrap().community.id
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -62,14 +65,14 @@ function assertCommentFederation(
|
|||
}
|
||||
|
||||
test('Create a comment', async () => {
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||
expect(commentRes.comment_view.comment.content).toBeDefined();
|
||||
expect(commentRes.comment_view.community.local).toBe(false);
|
||||
expect(commentRes.comment_view.creator.local).toBe(true);
|
||||
expect(commentRes.comment_view.counts.score).toBe(1);
|
||||
|
||||
// Make sure that comment is liked on beta
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
|
||||
expect(betaComment).toBeDefined();
|
||||
expect(betaComment.community.local).toBe(true);
|
||||
expect(betaComment.creator.local).toBe(false);
|
||||
|
@ -78,15 +81,15 @@ test('Create a comment', async () => {
|
|||
});
|
||||
|
||||
test('Create a comment in a non-existent post', async () => {
|
||||
let commentRes = await createComment(alpha, -1);
|
||||
expect(commentRes).toStrictEqual({ error: 'couldnt_find_post' });
|
||||
let commentRes = await createComment(alpha, -1, None) as any;
|
||||
expect(commentRes.error).toBe('couldnt_find_post');
|
||||
});
|
||||
|
||||
test('Update a comment', async () => {
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||
// Federate the comment first
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
||||
assertCommentFederation(betaComment, commentRes.comment_view);
|
||||
assertCommentFederation(betaComment.unwrap(), commentRes.comment_view);
|
||||
|
||||
let updateCommentRes = await editComment(
|
||||
alpha,
|
||||
|
@ -102,7 +105,7 @@ test('Update a comment', async () => {
|
|||
let betaCommentUpdated = (await resolveComment(
|
||||
beta,
|
||||
commentRes.comment_view.comment
|
||||
)).comment;
|
||||
)).comment.unwrap();
|
||||
assertCommentFederation(
|
||||
betaCommentUpdated,
|
||||
updateCommentRes.comment_view
|
||||
|
@ -110,7 +113,7 @@ test('Update a comment', async () => {
|
|||
});
|
||||
|
||||
test('Delete a comment', async () => {
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||
|
||||
let deleteCommentRes = await deleteComment(
|
||||
alpha,
|
||||
|
@ -121,8 +124,8 @@ test('Delete a comment', async () => {
|
|||
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
||||
|
||||
// Make sure that comment is undefined on beta
|
||||
let betaCommentRes: any = await resolveComment(beta, commentRes.comment_view.comment);
|
||||
expect(betaCommentRes).toStrictEqual({ error: 'couldnt_find_object' });
|
||||
let betaCommentRes = await resolveComment(beta, commentRes.comment_view.comment) as any;
|
||||
expect(betaCommentRes.error).toBe('couldnt_find_object');
|
||||
|
||||
let undeleteCommentRes = await deleteComment(
|
||||
alpha,
|
||||
|
@ -132,7 +135,7 @@ test('Delete a comment', async () => {
|
|||
expect(undeleteCommentRes.comment_view.comment.deleted).toBe(false);
|
||||
|
||||
// Make sure that comment is undeleted on beta
|
||||
let betaComment2 = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
||||
let betaComment2 = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
|
||||
expect(betaComment2.comment.deleted).toBe(false);
|
||||
assertCommentFederation(
|
||||
betaComment2,
|
||||
|
@ -141,12 +144,12 @@ test('Delete a comment', async () => {
|
|||
});
|
||||
|
||||
test('Remove a comment from admin and community on the same instance', async () => {
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||
|
||||
// Get the id for beta
|
||||
let betaCommentId = (
|
||||
await resolveComment(beta, commentRes.comment_view.comment)
|
||||
).comment.comment.id;
|
||||
).comment.unwrap().comment.id;
|
||||
|
||||
// The beta admin removes it (the community lives on beta)
|
||||
let removeCommentRes = await removeComment(beta, true, betaCommentId);
|
||||
|
@ -154,17 +157,17 @@ test('Remove a comment from admin and community on the same instance', async ()
|
|||
expect(removeCommentRes.comment_view.comment.content).toBe("");
|
||||
|
||||
// Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
|
||||
let refetchedPost = await getPost(alpha, postRes.post_view.post.id);
|
||||
expect(refetchedPost.comments[0].comment.removed).toBe(true);
|
||||
let refetchedPostComments = await getComments(alpha, postRes.post_view.post.id);
|
||||
expect(refetchedPostComments.comments[0].comment.removed).toBe(true);
|
||||
|
||||
let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
|
||||
expect(unremoveCommentRes.comment_view.comment.removed).toBe(false);
|
||||
|
||||
// Make sure that comment is unremoved on beta
|
||||
let refetchedPost2 = await getPost(alpha, postRes.post_view.post.id);
|
||||
expect(refetchedPost2.comments[0].comment.removed).toBe(false);
|
||||
let refetchedPostComments2 = await getComments(alpha, postRes.post_view.post.id);
|
||||
expect(refetchedPostComments2.comments[0].comment.removed).toBe(false);
|
||||
assertCommentFederation(
|
||||
refetchedPost2.comments[0],
|
||||
refetchedPostComments2.comments[0],
|
||||
unremoveCommentRes.comment_view
|
||||
);
|
||||
});
|
||||
|
@ -182,11 +185,11 @@ test('Remove a comment from admin and community on different instance', async ()
|
|||
newAlphaApi,
|
||||
newCommunity.community_view.community.id
|
||||
);
|
||||
let commentRes = await createComment(newAlphaApi, newPost.post_view.post.id);
|
||||
let commentRes = await createComment(newAlphaApi, newPost.post_view.post.id, None);
|
||||
expect(commentRes.comment_view.comment.content).toBeDefined();
|
||||
|
||||
// Beta searches that to cache it, then removes it
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
|
||||
let removeCommentRes = await removeComment(
|
||||
beta,
|
||||
true,
|
||||
|
@ -195,18 +198,18 @@ test('Remove a comment from admin and community on different instance', async ()
|
|||
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
|
||||
|
||||
// Make sure its not removed on alpha
|
||||
let refetchedPost = await getPost(newAlphaApi, newPost.post_view.post.id);
|
||||
expect(refetchedPost.comments[0].comment.removed).toBe(false);
|
||||
assertCommentFederation(refetchedPost.comments[0], commentRes.comment_view);
|
||||
let refetchedPostComments = await getComments(alpha, newPost.post_view.post.id);
|
||||
expect(refetchedPostComments.comments[0].comment.removed).toBe(false);
|
||||
assertCommentFederation(refetchedPostComments.comments[0], commentRes.comment_view);
|
||||
});
|
||||
|
||||
test('Unlike a comment', async () => {
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||
let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
|
||||
expect(unlike.comment_view.counts.score).toBe(0);
|
||||
|
||||
// Make sure that post is unliked on beta
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
|
||||
expect(betaComment).toBeDefined();
|
||||
expect(betaComment.community.local).toBe(true);
|
||||
expect(betaComment.creator.local).toBe(false);
|
||||
|
@ -214,23 +217,23 @@ test('Unlike a comment', async () => {
|
|||
});
|
||||
|
||||
test('Federated comment like', async () => {
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||
|
||||
// Find the comment on beta
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
|
||||
|
||||
let like = await likeComment(beta, 1, betaComment.comment);
|
||||
expect(like.comment_view.counts.score).toBe(2);
|
||||
|
||||
// Get the post from alpha, check the likes
|
||||
let post = await getPost(alpha, postRes.post_view.post.id);
|
||||
expect(post.comments[0].counts.score).toBe(2);
|
||||
let postComments = await getComments(alpha, postRes.post_view.post.id);
|
||||
expect(postComments.comments[0].counts.score).toBe(2);
|
||||
});
|
||||
|
||||
test('Reply to a comment', async () => {
|
||||
// Create a comment on alpha, find it on beta
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
|
||||
|
||||
// find that comment id on beta
|
||||
|
||||
|
@ -238,22 +241,22 @@ test('Reply to a comment', async () => {
|
|||
let replyRes = await createComment(
|
||||
beta,
|
||||
betaComment.post.id,
|
||||
betaComment.comment.id
|
||||
Some(betaComment.comment.id)
|
||||
);
|
||||
expect(replyRes.comment_view.comment.content).toBeDefined();
|
||||
expect(replyRes.comment_view.community.local).toBe(true);
|
||||
expect(replyRes.comment_view.creator.local).toBe(true);
|
||||
expect(replyRes.comment_view.comment.parent_id).toBe(betaComment.comment.id);
|
||||
expect(getCommentParentId(replyRes.comment_view.comment).unwrap()).toBe(betaComment.comment.id);
|
||||
expect(replyRes.comment_view.counts.score).toBe(1);
|
||||
|
||||
// Make sure that comment is seen on alpha
|
||||
// TODO not sure why, but a searchComment back to alpha, for the ap_id of betas
|
||||
// comment, isn't working.
|
||||
// let searchAlpha = await searchComment(alpha, replyRes.comment);
|
||||
let post = await getPost(alpha, postRes.post_view.post.id);
|
||||
let alphaComment = post.comments[0];
|
||||
let postComments = await getComments(alpha, postRes.post_view.post.id);
|
||||
let alphaComment = postComments.comments[0];
|
||||
expect(alphaComment.comment.content).toBeDefined();
|
||||
expect(alphaComment.comment.parent_id).toBe(post.comments[1].comment.id);
|
||||
expect(getCommentParentId(alphaComment.comment).unwrap()).toBe(postComments.comments[1].comment.id);
|
||||
expect(alphaComment.community.local).toBe(false);
|
||||
expect(alphaComment.creator.local).toBe(false);
|
||||
expect(alphaComment.counts.score).toBe(1);
|
||||
|
@ -263,11 +266,11 @@ test('Reply to a comment', async () => {
|
|||
test('Mention beta', async () => {
|
||||
// Create a mention on alpha
|
||||
let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8551';
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||
let mentionRes = await createComment(
|
||||
alpha,
|
||||
postRes.post_view.post.id,
|
||||
commentRes.comment_view.comment.id,
|
||||
Some(commentRes.comment_view.comment.id),
|
||||
mentionContent
|
||||
);
|
||||
expect(mentionRes.comment_view.comment.content).toBeDefined();
|
||||
|
@ -283,8 +286,8 @@ test('Mention beta', async () => {
|
|||
});
|
||||
|
||||
test('Comment Search', async () => {
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
||||
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
|
||||
assertCommentFederation(betaComment, commentRes.comment_view);
|
||||
});
|
||||
|
||||
|
@ -295,14 +298,14 @@ test('A and G subscribe to B (center) A posts, G mentions B, it gets announced t
|
|||
expect(alphaPost.post_view.community.local).toBe(true);
|
||||
|
||||
// Make sure gamma sees it
|
||||
let gammaPost = (await resolvePost(gamma, alphaPost.post_view.post)).post;
|
||||
let gammaPost = (await resolvePost(gamma, alphaPost.post_view.post)).post.unwrap();
|
||||
|
||||
let commentContent =
|
||||
'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8551';
|
||||
let commentRes = await createComment(
|
||||
gamma,
|
||||
gammaPost.post.id,
|
||||
undefined,
|
||||
None,
|
||||
commentContent
|
||||
);
|
||||
expect(commentRes.comment_view.comment.content).toBe(commentContent);
|
||||
|
@ -311,12 +314,12 @@ test('A and G subscribe to B (center) A posts, G mentions B, it gets announced t
|
|||
expect(commentRes.comment_view.counts.score).toBe(1);
|
||||
|
||||
// Make sure alpha sees it
|
||||
let alphaPost2 = await getPost(alpha, alphaPost.post_view.post.id);
|
||||
expect(alphaPost2.comments[0].comment.content).toBe(commentContent);
|
||||
expect(alphaPost2.comments[0].community.local).toBe(true);
|
||||
expect(alphaPost2.comments[0].creator.local).toBe(false);
|
||||
expect(alphaPost2.comments[0].counts.score).toBe(1);
|
||||
assertCommentFederation(alphaPost2.comments[0], commentRes.comment_view);
|
||||
let alphaPostComments2 = await getComments(alpha, alphaPost.post_view.post.id);
|
||||
expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
|
||||
expect(alphaPostComments2.comments[0].community.local).toBe(true);
|
||||
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
|
||||
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
|
||||
assertCommentFederation(alphaPostComments2.comments[0], commentRes.comment_view);
|
||||
|
||||
// Make sure beta has mentions
|
||||
let mentionsRes = await getMentions(beta);
|
||||
|
@ -342,9 +345,9 @@ test('Check that activity from another instance is sent to third instance', asyn
|
|||
expect(betaPost.post_view.community.local).toBe(true);
|
||||
|
||||
// Make sure gamma and alpha see it
|
||||
let gammaPost = (await resolvePost(gamma, betaPost.post_view.post)).post;
|
||||
let gammaPost = (await resolvePost(gamma, betaPost.post_view.post)).post.unwrap();
|
||||
expect(gammaPost.post).toBeDefined();
|
||||
let alphaPost = (await resolvePost(alpha, betaPost.post_view.post)).post;
|
||||
let alphaPost = (await resolvePost(alpha, betaPost.post_view.post)).post.unwrap();
|
||||
expect(alphaPost.post).toBeDefined();
|
||||
|
||||
// The bug: gamma comments, and alpha should see it.
|
||||
|
@ -352,7 +355,7 @@ test('Check that activity from another instance is sent to third instance', asyn
|
|||
let commentRes = await createComment(
|
||||
gamma,
|
||||
gammaPost.post.id,
|
||||
undefined,
|
||||
None,
|
||||
commentContent
|
||||
);
|
||||
expect(commentRes.comment_view.comment.content).toBe(commentContent);
|
||||
|
@ -361,12 +364,12 @@ test('Check that activity from another instance is sent to third instance', asyn
|
|||
expect(commentRes.comment_view.counts.score).toBe(1);
|
||||
|
||||
// Make sure alpha sees it
|
||||
let alphaPost2 = await getPost(alpha, alphaPost.post.id);
|
||||
expect(alphaPost2.comments[0].comment.content).toBe(commentContent);
|
||||
expect(alphaPost2.comments[0].community.local).toBe(false);
|
||||
expect(alphaPost2.comments[0].creator.local).toBe(false);
|
||||
expect(alphaPost2.comments[0].counts.score).toBe(1);
|
||||
assertCommentFederation(alphaPost2.comments[0], commentRes.comment_view);
|
||||
let alphaPostComments2 = await getComments(alpha, alphaPost.post.id);
|
||||
expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
|
||||
expect(alphaPostComments2.comments[0].community.local).toBe(false);
|
||||
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
|
||||
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
|
||||
assertCommentFederation(alphaPostComments2.comments[0], commentRes.comment_view);
|
||||
|
||||
await unfollowRemotes(alpha);
|
||||
await unfollowRemotes(gamma);
|
||||
|
@ -376,7 +379,7 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
|
|||
// Unfollow all remote communities
|
||||
let site = await unfollowRemotes(alpha);
|
||||
expect(
|
||||
site.my_user.follows.filter(c => c.community.local == false).length
|
||||
site.my_user.unwrap().follows.filter(c => c.community.local == false).length
|
||||
).toBe(0);
|
||||
|
||||
// B creates a post, and two comments, should be invisible to A
|
||||
|
@ -387,7 +390,7 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
|
|||
let parentCommentRes = await createComment(
|
||||
beta,
|
||||
postRes.post_view.post.id,
|
||||
undefined,
|
||||
None,
|
||||
parentCommentContent
|
||||
);
|
||||
expect(parentCommentRes.comment_view.comment.content).toBe(
|
||||
|
@ -399,7 +402,7 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
|
|||
let childCommentRes = await createComment(
|
||||
beta,
|
||||
postRes.post_view.post.id,
|
||||
parentCommentRes.comment_view.comment.id,
|
||||
Some(parentCommentRes.comment_view.comment.id),
|
||||
childCommentContent
|
||||
);
|
||||
expect(childCommentRes.comment_view.comment.content).toBe(
|
||||
|
@ -421,12 +424,13 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
|
|||
expect(updateRes.comment_view.comment.content).toBe(updatedCommentContent);
|
||||
|
||||
// Get the post from alpha
|
||||
let alphaPostB = (await resolvePost(alpha, postRes.post_view.post)).post;
|
||||
let alphaPostB = (await resolvePost(alpha, postRes.post_view.post)).post.unwrap();
|
||||
|
||||
let alphaPost = await getPost(alpha, alphaPostB.post.id);
|
||||
let alphaPostComments = await getComments(alpha, alphaPostB.post.id);
|
||||
expect(alphaPost.post_view.post.name).toBeDefined();
|
||||
assertCommentFederation(alphaPost.comments[1], parentCommentRes.comment_view);
|
||||
assertCommentFederation(alphaPost.comments[0], updateRes.comment_view);
|
||||
assertCommentFederation(alphaPostComments.comments[1], parentCommentRes.comment_view);
|
||||
assertCommentFederation(alphaPostComments.comments[0], updateRes.comment_view);
|
||||
expect(alphaPost.post_view.community.local).toBe(false);
|
||||
expect(alphaPost.post_view.creator.local).toBe(false);
|
||||
|
||||
|
@ -435,13 +439,13 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
|
|||
|
||||
|
||||
test('Report a comment', async () => {
|
||||
let betaCommunity = (await resolveBetaCommunity(beta)).community;
|
||||
let betaCommunity = (await resolveBetaCommunity(beta)).community.unwrap();
|
||||
let postRes = (await createPost(beta, betaCommunity.community.id)).post_view.post;
|
||||
expect(postRes).toBeDefined();
|
||||
let commentRes = (await createComment(beta, postRes.id)).comment_view.comment;
|
||||
let commentRes = (await createComment(beta, postRes.id, None)).comment_view.comment;
|
||||
expect(commentRes).toBeDefined();
|
||||
|
||||
let alphaComment = (await resolveComment(alpha, commentRes)).comment.comment;
|
||||
let alphaComment = (await resolveComment(alpha, commentRes)).comment.unwrap().comment;
|
||||
let alphaReport = (await reportComment(alpha, alphaComment.id, randomString(10)))
|
||||
.comment_report_view.comment_report;
|
||||
|
||||
|
@ -451,3 +455,7 @@ test('Report a comment', async () => {
|
|||
expect(betaReport.original_comment_text).toBe(alphaReport.original_comment_text);
|
||||
expect(betaReport.reason).toBe(alphaReport.reason);
|
||||
});
|
||||
function N(gamma: API, id: number, N: any, commentContent: string) {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
jest.setTimeout(120000);
|
||||
import { CommunityView } from 'lemmy-js-client';
|
||||
|
||||
import {
|
||||
alpha,
|
||||
beta,
|
||||
|
@ -10,7 +12,6 @@ import {
|
|||
getCommunity,
|
||||
followCommunity,
|
||||
} from './shared';
|
||||
import { CommunityView } from 'lemmy-js-client';
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupLogins();
|
||||
|
@ -23,11 +24,11 @@ function assertCommunityFederation(
|
|||
expect(communityOne.community.actor_id).toBe(communityTwo.community.actor_id);
|
||||
expect(communityOne.community.name).toBe(communityTwo.community.name);
|
||||
expect(communityOne.community.title).toBe(communityTwo.community.title);
|
||||
expect(communityOne.community.description).toBe(
|
||||
communityTwo.community.description
|
||||
expect(communityOne.community.description.unwrapOr("none")).toBe(
|
||||
communityTwo.community.description.unwrapOr("none")
|
||||
);
|
||||
expect(communityOne.community.icon).toBe(communityTwo.community.icon);
|
||||
expect(communityOne.community.banner).toBe(communityTwo.community.banner);
|
||||
expect(communityOne.community.icon.unwrapOr("none")).toBe(communityTwo.community.icon.unwrapOr("none"));
|
||||
expect(communityOne.community.banner.unwrapOr("none")).toBe(communityTwo.community.banner.unwrapOr("none"));
|
||||
expect(communityOne.community.published).toBe(
|
||||
communityTwo.community.published
|
||||
);
|
||||
|
@ -47,7 +48,7 @@ test('Create community', async () => {
|
|||
|
||||
// Cache the community on beta, make sure it has the other fields
|
||||
let searchShort = `!${prevName}@lemmy-alpha:8541`;
|
||||
let betaCommunity = (await resolveCommunity(beta, searchShort)).community;
|
||||
let betaCommunity = (await resolveCommunity(beta, searchShort)).community.unwrap();
|
||||
assertCommunityFederation(betaCommunity, communityRes.community_view);
|
||||
});
|
||||
|
||||
|
@ -56,7 +57,7 @@ test('Delete community', async () => {
|
|||
|
||||
// Cache the community on Alpha
|
||||
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
|
||||
let alphaCommunity = (await resolveCommunity(alpha, searchShort)).community;
|
||||
let alphaCommunity = (await resolveCommunity(alpha, searchShort)).community.unwrap();
|
||||
assertCommunityFederation(alphaCommunity, communityRes.community_view);
|
||||
|
||||
// Follow the community from alpha
|
||||
|
@ -107,7 +108,7 @@ test('Remove community', async () => {
|
|||
|
||||
// Cache the community on Alpha
|
||||
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
|
||||
let alphaCommunity = (await resolveCommunity(alpha, searchShort)).community;
|
||||
let alphaCommunity = (await resolveCommunity(alpha, searchShort)).community.unwrap();
|
||||
assertCommunityFederation(alphaCommunity, communityRes.community_view);
|
||||
|
||||
// Follow the community from alpha
|
||||
|
@ -158,6 +159,6 @@ test('Search for beta community', async () => {
|
|||
expect(communityRes.community_view.community.name).toBeDefined();
|
||||
|
||||
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
|
||||
let alphaCommunity = (await resolveCommunity(alpha, searchShort)).community;
|
||||
let alphaCommunity = (await resolveCommunity(alpha, searchShort)).community.unwrap();
|
||||
assertCommunityFederation(alphaCommunity, communityRes.community_view);
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@ afterAll(async () => {
|
|||
});
|
||||
|
||||
test('Follow federated community', async () => {
|
||||
let betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
||||
let betaCommunity = (await resolveBetaCommunity(alpha)).community.unwrap();
|
||||
let follow = await followCommunity(
|
||||
alpha,
|
||||
true,
|
||||
|
@ -33,11 +33,11 @@ test('Follow federated community', async () => {
|
|||
|
||||
// Check it from local
|
||||
let site = await getSite(alpha);
|
||||
let remoteCommunityId = site.my_user.follows.find(
|
||||
let remoteCommunityId = site.my_user.unwrap().follows.find(
|
||||
c => c.community.local == false
|
||||
).community.id;
|
||||
expect(remoteCommunityId).toBeDefined();
|
||||
expect(site.my_user.follows.length).toBe(1);
|
||||
expect(site.my_user.unwrap().follows.length).toBe(1);
|
||||
|
||||
// Test an unfollow
|
||||
let unfollow = await followCommunity(alpha, false, remoteCommunityId);
|
||||
|
@ -45,5 +45,5 @@ test('Follow federated community', async () => {
|
|||
|
||||
// Make sure you are unsubbed locally
|
||||
let siteUnfollowCheck = await getSite(alpha);
|
||||
expect(siteUnfollowCheck.my_user.follows.length).toBe(0);
|
||||
expect(siteUnfollowCheck.my_user.unwrap().follows.length).toBe(0);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
jest.setTimeout(120000);
|
||||
import {None} from '@sniptt/monads';
|
||||
import { PostView, CommunityView } from 'lemmy-js-client';
|
||||
import {
|
||||
alpha,
|
||||
beta,
|
||||
|
@ -32,13 +34,12 @@ import {
|
|||
getSite,
|
||||
unfollows
|
||||
} from './shared';
|
||||
import { PostView, CommunityView } from 'lemmy-js-client';
|
||||
|
||||
let betaCommunity: CommunityView;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupLogins();
|
||||
betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
||||
betaCommunity = (await resolveBetaCommunity(alpha)).community.unwrap();
|
||||
expect(betaCommunity).toBeDefined();
|
||||
await unfollows();
|
||||
});
|
||||
|
@ -50,12 +51,12 @@ afterAll(async () => {
|
|||
function assertPostFederation(postOne: PostView, postTwo: PostView) {
|
||||
expect(postOne.post.ap_id).toBe(postTwo.post.ap_id);
|
||||
expect(postOne.post.name).toBe(postTwo.post.name);
|
||||
expect(postOne.post.body).toBe(postTwo.post.body);
|
||||
expect(postOne.post.url).toBe(postTwo.post.url);
|
||||
expect(postOne.post.body.unwrapOr("none")).toBe(postTwo.post.body.unwrapOr("none"));
|
||||
expect(postOne.post.url.unwrapOr("none")).toBe(postTwo.post.url.unwrapOr("none"));
|
||||
expect(postOne.post.nsfw).toBe(postTwo.post.nsfw);
|
||||
expect(postOne.post.embed_title).toBe(postTwo.post.embed_title);
|
||||
expect(postOne.post.embed_description).toBe(postTwo.post.embed_description);
|
||||
expect(postOne.post.embed_html).toBe(postTwo.post.embed_html);
|
||||
expect(postOne.post.embed_title.unwrapOr("none")).toBe(postTwo.post.embed_title.unwrapOr("none"));
|
||||
expect(postOne.post.embed_description.unwrapOr("none")).toBe(postTwo.post.embed_description.unwrapOr("none"));
|
||||
expect(postOne.post.embed_html.unwrapOr("none")).toBe(postTwo.post.embed_html.unwrapOr("none"));
|
||||
expect(postOne.post.published).toBe(postTwo.post.published);
|
||||
expect(postOne.community.actor_id).toBe(postTwo.community.actor_id);
|
||||
expect(postOne.post.locked).toBe(postTwo.post.locked);
|
||||
|
@ -71,7 +72,7 @@ test('Create a post', async () => {
|
|||
expect(postRes.post_view.counts.score).toBe(1);
|
||||
|
||||
// Make sure that post is liked on beta
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
|
||||
expect(betaPost).toBeDefined();
|
||||
expect(betaPost.community.local).toBe(true);
|
||||
|
@ -81,16 +82,16 @@ test('Create a post', async () => {
|
|||
|
||||
// Delta only follows beta, so it should not see an alpha ap_id
|
||||
let deltaPost = (await resolvePost(delta, postRes.post_view.post)).post;
|
||||
expect(deltaPost).toBeUndefined();
|
||||
expect(deltaPost.isNone()).toBe(true)
|
||||
|
||||
// Epsilon has alpha blocked, it should not see the alpha post
|
||||
let epsilonPost = (await resolvePost(epsilon, postRes.post_view.post)).post;
|
||||
expect(epsilonPost).toBeUndefined();
|
||||
expect(epsilonPost.isNone()).toBe(true);
|
||||
});
|
||||
|
||||
test('Create a post in a non-existent community', async () => {
|
||||
let postRes = await createPost(alpha, -2);
|
||||
expect(postRes).toStrictEqual({ error: 'couldnt_find_community' });
|
||||
let postRes = await createPost(alpha, -2) as any;
|
||||
expect(postRes.error).toBe('couldnt_find_community');
|
||||
});
|
||||
|
||||
test('Unlike a post', async () => {
|
||||
|
@ -103,7 +104,7 @@ test('Unlike a post', async () => {
|
|||
expect(unlike2.post_view.counts.score).toBe(0);
|
||||
|
||||
// Make sure that post is unliked on beta
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
expect(betaPost).toBeDefined();
|
||||
expect(betaPost.community.local).toBe(true);
|
||||
expect(betaPost.creator.local).toBe(false);
|
||||
|
@ -121,26 +122,26 @@ test('Update a post', async () => {
|
|||
expect(updatedPost.post_view.creator.local).toBe(true);
|
||||
|
||||
// Make sure that post is updated on beta
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
expect(betaPost.community.local).toBe(true);
|
||||
expect(betaPost.creator.local).toBe(false);
|
||||
expect(betaPost.post.name).toBe(updatedName);
|
||||
assertPostFederation(betaPost, updatedPost.post_view);
|
||||
|
||||
// Make sure lemmy beta cannot update the post
|
||||
let updatedPostBeta = await editPost(beta, betaPost.post);
|
||||
expect(updatedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
|
||||
let updatedPostBeta = await editPost(beta, betaPost.post) as any;
|
||||
expect(updatedPostBeta.error).toBe('no_post_edit_allowed');
|
||||
});
|
||||
|
||||
test('Sticky a post', async () => {
|
||||
let postRes = await createPost(alpha, betaCommunity.community.id);
|
||||
|
||||
let betaPost1 = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost1 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
let stickiedPostRes = await stickyPost(beta, true, betaPost1.post);
|
||||
expect(stickiedPostRes.post_view.post.stickied).toBe(true);
|
||||
|
||||
// Make sure that post is stickied on beta
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
expect(betaPost.community.local).toBe(true);
|
||||
expect(betaPost.creator.local).toBe(false);
|
||||
expect(betaPost.post.stickied).toBe(true);
|
||||
|
@ -150,15 +151,15 @@ test('Sticky a post', async () => {
|
|||
expect(unstickiedPost.post_view.post.stickied).toBe(false);
|
||||
|
||||
// Make sure that post is unstickied on beta
|
||||
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
expect(betaPost2.community.local).toBe(true);
|
||||
expect(betaPost2.creator.local).toBe(false);
|
||||
expect(betaPost2.post.stickied).toBe(false);
|
||||
|
||||
// Make sure that gamma cannot sticky the post on beta
|
||||
let gammaPost = (await resolvePost(gamma, postRes.post_view.post)).post;
|
||||
let gammaPost = (await resolvePost(gamma, postRes.post_view.post)).post.unwrap();
|
||||
let gammaTrySticky = await stickyPost(gamma, true, gammaPost.post);
|
||||
let betaPost3 = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost3 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
expect(gammaTrySticky.post_view.post.stickied).toBe(true);
|
||||
expect(betaPost3.post.stickied).toBe(false);
|
||||
});
|
||||
|
@ -168,7 +169,7 @@ test('Lock a post', async () => {
|
|||
let postRes = await createPost(alpha, betaCommunity.community.id);
|
||||
|
||||
// Lock the post
|
||||
let betaPost1 = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost1 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
let lockedPostRes = await lockPost(beta, true, betaPost1.post);
|
||||
expect(lockedPostRes.post_view.post.locked).toBe(true);
|
||||
|
||||
|
@ -178,7 +179,7 @@ test('Lock a post', async () => {
|
|||
expect(alphaPost1.post.locked).toBe(true);
|
||||
|
||||
// Try to make a new comment there, on alpha
|
||||
let comment: any = await createComment(alpha, alphaPost1.post.id);
|
||||
let comment: any = await createComment(alpha, alphaPost1.post.id, None);
|
||||
expect(comment['error']).toBe('locked');
|
||||
|
||||
// Unlock a post
|
||||
|
@ -193,7 +194,7 @@ test('Lock a post', async () => {
|
|||
expect(alphaPost2.post.locked).toBe(false);
|
||||
|
||||
// Try to create a new comment, on alpha
|
||||
let commentAlpha = await createComment(alpha, alphaPost1.post.id);
|
||||
let commentAlpha = await createComment(alpha, alphaPost1.post.id, None);
|
||||
expect(commentAlpha).toBeDefined();
|
||||
});
|
||||
|
||||
|
@ -208,32 +209,32 @@ test('Delete a post', async () => {
|
|||
// Make sure lemmy beta sees post is deleted
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
// This will be undefined because of the tombstone
|
||||
expect(betaPost).toBeUndefined();
|
||||
expect(betaPost.isNone()).toBe(true);
|
||||
|
||||
// Undelete
|
||||
let undeletedPost = await deletePost(alpha, false, postRes.post_view.post);
|
||||
expect(undeletedPost.post_view.post.deleted).toBe(false);
|
||||
|
||||
// Make sure lemmy beta sees post is undeleted
|
||||
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
expect(betaPost2.post.deleted).toBe(false);
|
||||
assertPostFederation(betaPost2, undeletedPost.post_view);
|
||||
|
||||
// Make sure lemmy beta cannot delete the post
|
||||
let deletedPostBeta = await deletePost(beta, true, betaPost2.post);
|
||||
expect(deletedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
|
||||
let deletedPostBeta = await deletePost(beta, true, betaPost2.post) as any;
|
||||
expect(deletedPostBeta.error).toStrictEqual('no_post_edit_allowed');
|
||||
});
|
||||
|
||||
test('Remove a post from admin and community on different instance', async () => {
|
||||
let postRes = await createPost(gamma, betaCommunity.community.id);
|
||||
|
||||
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post;
|
||||
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post.unwrap();
|
||||
let removedPost = await removePost(alpha, true, alphaPost.post);
|
||||
expect(removedPost.post_view.post.removed).toBe(true);
|
||||
expect(removedPost.post_view.post.name).toBe(postRes.post_view.post.name);
|
||||
|
||||
// Make sure lemmy beta sees post is NOT removed
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
expect(betaPost.post.removed).toBe(false);
|
||||
|
||||
// Undelete
|
||||
|
@ -241,7 +242,7 @@ test('Remove a post from admin and community on different instance', async () =>
|
|||
expect(undeletedPost.post_view.post.removed).toBe(false);
|
||||
|
||||
// Make sure lemmy beta sees post is undeleted
|
||||
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
expect(betaPost2.post.removed).toBe(false);
|
||||
assertPostFederation(betaPost2, undeletedPost.post_view);
|
||||
});
|
||||
|
@ -261,7 +262,7 @@ test('Remove a post from admin and community on same instance', async () => {
|
|||
expect(removePostRes.post_view.post.removed).toBe(true);
|
||||
|
||||
// Make sure lemmy alpha sees post is removed
|
||||
let alphaPost = await getPost(alpha, postRes.post_view.post.id);
|
||||
// let alphaPost = await getPost(alpha, postRes.post_view.post.id);
|
||||
// expect(alphaPost.post_view.post.removed).toBe(true); // TODO this shouldn't be commented
|
||||
// assertPostFederation(alphaPost.post_view, removePostRes.post_view);
|
||||
|
||||
|
@ -281,8 +282,7 @@ test('Search for a post', async () => {
|
|||
let postRes = await createPost(alpha, betaCommunity.community.id);
|
||||
expect(postRes.post_view.post).toBeDefined();
|
||||
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
|
||||
expect(betaPost.post.name).toBeDefined();
|
||||
});
|
||||
|
||||
|
@ -294,9 +294,9 @@ test('Enforce site ban for federated user', async () => {
|
|||
client: alpha.client,
|
||||
auth: alphaUserJwt.jwt,
|
||||
};
|
||||
let alphaUserActorId = (await getSite(alpha_user)).my_user.local_user_view.person.actor_id;
|
||||
let alphaUserActorId = (await getSite(alpha_user)).my_user.unwrap().local_user_view.person.actor_id;
|
||||
expect(alphaUserActorId).toBeDefined();
|
||||
let alphaPerson = (await resolvePerson(alpha_user, alphaUserActorId)).person;
|
||||
let alphaPerson = (await resolvePerson(alpha_user, alphaUserActorId)).person.unwrap();
|
||||
expect(alphaPerson).toBeDefined();
|
||||
|
||||
// alpha makes post in beta community, it federates to beta instance
|
||||
|
@ -310,7 +310,7 @@ test('Enforce site ban for federated user', async () => {
|
|||
|
||||
// alpha ban should be federated to beta
|
||||
let alphaUserOnBeta1 = await resolvePerson(beta, alphaUserActorId);
|
||||
expect(alphaUserOnBeta1.person.person.banned).toBe(true);
|
||||
expect(alphaUserOnBeta1.person.unwrap().person.banned).toBe(true);
|
||||
|
||||
// existing alpha post should be removed on beta
|
||||
let searchBeta2 = await searchPostLocal(beta, postRes1.post_view.post);
|
||||
|
@ -326,12 +326,12 @@ test('Enforce site ban for federated user', async () => {
|
|||
expect(searchBeta3.posts[0]).toBeDefined();
|
||||
|
||||
let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId)
|
||||
expect(alphaUserOnBeta2.person.person.banned).toBe(false);
|
||||
expect(alphaUserOnBeta2.person.unwrap().person.banned).toBe(false);
|
||||
});
|
||||
|
||||
test('Enforce community ban for federated user', async () => {
|
||||
let alphaShortname = `@lemmy_alpha@lemmy-alpha:8541`;
|
||||
let alphaPerson = (await resolvePerson(beta, alphaShortname)).person;
|
||||
let alphaPerson = (await resolvePerson(beta, alphaShortname)).person.unwrap();
|
||||
expect(alphaPerson).toBeDefined();
|
||||
|
||||
// make a post in beta, it goes through
|
||||
|
@ -376,16 +376,16 @@ test('A and G subscribe to B (center) A posts, it gets announced to G', async ()
|
|||
let postRes = await createPost(alpha, betaCommunity.community.id);
|
||||
expect(postRes.post_view.post).toBeDefined();
|
||||
|
||||
let betaPost = (await resolvePost(gamma, postRes.post_view.post)).post;
|
||||
let betaPost = (await resolvePost(gamma, postRes.post_view.post)).post.unwrap();
|
||||
expect(betaPost.post.name).toBeDefined();
|
||||
});
|
||||
|
||||
test('Report a post', async () => {
|
||||
let betaCommunity = (await resolveBetaCommunity(beta)).community;
|
||||
let betaCommunity = (await resolveBetaCommunity(beta)).community.unwrap();
|
||||
let postRes = await createPost(beta, betaCommunity.community.id);
|
||||
expect(postRes.post_view.post).toBeDefined();
|
||||
|
||||
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post;
|
||||
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post.unwrap();
|
||||
let alphaReport = (await reportPost(alpha, alphaPost.post.id, randomString(10)))
|
||||
.post_report_view.post_report;
|
||||
|
||||
|
@ -393,7 +393,7 @@ test('Report a post', async () => {
|
|||
expect(betaReport).toBeDefined();
|
||||
expect(betaReport.resolved).toBe(false);
|
||||
expect(betaReport.original_post_name).toBe(alphaReport.original_post_name);
|
||||
expect(betaReport.original_post_url).toBe(alphaReport.original_post_url);
|
||||
expect(betaReport.original_post_body).toBe(alphaReport.original_post_body);
|
||||
expect(betaReport.original_post_url.unwrapOr("none")).toBe(alphaReport.original_post_url.unwrapOr("none"));
|
||||
expect(betaReport.original_post_body.unwrapOr("none")).toBe(alphaReport.original_post_body.unwrapOr("none"));
|
||||
expect(betaReport.reason).toBe(alphaReport.reason);
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import {None, Some, Option} from '@sniptt/monads';
|
||||
import {
|
||||
Login,
|
||||
LoginResponse,
|
||||
|
@ -58,65 +59,74 @@ import {
|
|||
ListCommentReports,
|
||||
ListCommentReportsResponse,
|
||||
DeleteAccount,
|
||||
DeleteAccountResponse
|
||||
DeleteAccountResponse,
|
||||
EditSite,
|
||||
CommentSortType,
|
||||
GetComments,
|
||||
GetCommentsResponse
|
||||
} from 'lemmy-js-client';
|
||||
|
||||
export interface API {
|
||||
client: LemmyHttp;
|
||||
auth?: string;
|
||||
auth: Option<string>;
|
||||
}
|
||||
|
||||
export let alpha: API = {
|
||||
client: new LemmyHttp('http://127.0.0.1:8541'),
|
||||
auth: None,
|
||||
};
|
||||
|
||||
export let beta: API = {
|
||||
client: new LemmyHttp('http://127.0.0.1:8551'),
|
||||
auth: None,
|
||||
};
|
||||
|
||||
export let gamma: API = {
|
||||
client: new LemmyHttp('http://127.0.0.1:8561'),
|
||||
auth: None,
|
||||
};
|
||||
|
||||
export let delta: API = {
|
||||
client: new LemmyHttp('http://127.0.0.1:8571'),
|
||||
auth: None,
|
||||
};
|
||||
|
||||
export let epsilon: API = {
|
||||
client: new LemmyHttp('http://127.0.0.1:8581'),
|
||||
auth: None,
|
||||
};
|
||||
|
||||
const password = 'lemmylemmy'
|
||||
|
||||
export async function setupLogins() {
|
||||
let formAlpha: Login = {
|
||||
let formAlpha = new Login({
|
||||
username_or_email: 'lemmy_alpha',
|
||||
password,
|
||||
};
|
||||
});
|
||||
let resAlpha = alpha.client.login(formAlpha);
|
||||
|
||||
let formBeta = {
|
||||
let formBeta = new Login({
|
||||
username_or_email: 'lemmy_beta',
|
||||
password,
|
||||
};
|
||||
});
|
||||
let resBeta = beta.client.login(formBeta);
|
||||
|
||||
let formGamma = {
|
||||
let formGamma = new Login({
|
||||
username_or_email: 'lemmy_gamma',
|
||||
password,
|
||||
};
|
||||
});
|
||||
let resGamma = gamma.client.login(formGamma);
|
||||
|
||||
let formDelta = {
|
||||
let formDelta = new Login({
|
||||
username_or_email: 'lemmy_delta',
|
||||
password,
|
||||
};
|
||||
});
|
||||
let resDelta = delta.client.login(formDelta);
|
||||
|
||||
let formEpsilon = {
|
||||
let formEpsilon = new Login({
|
||||
username_or_email: 'lemmy_epsilon',
|
||||
password,
|
||||
};
|
||||
});
|
||||
let resEpsilon = epsilon.client.login(formEpsilon);
|
||||
|
||||
let res = await Promise.all([
|
||||
|
@ -133,12 +143,36 @@ export async function setupLogins() {
|
|||
delta.auth = res[3].jwt;
|
||||
epsilon.auth = res[4].jwt;
|
||||
|
||||
// regstration applications are now enabled by default, need to disable them
|
||||
await alpha.client.editSite({ require_application: false, auth: alpha.auth});
|
||||
await beta.client.editSite({ require_application: false, auth: beta.auth});
|
||||
await gamma.client.editSite({ require_application: false, auth: gamma.auth});
|
||||
await delta.client.editSite({ require_application: false, auth: delta.auth});
|
||||
await epsilon.client.editSite({ require_application: false, auth: epsilon.auth});
|
||||
// Registration applications are now enabled by default, need to disable them
|
||||
let editSiteForm = new EditSite({
|
||||
name: None,
|
||||
sidebar: None,
|
||||
description: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
enable_downvotes: None,
|
||||
open_registration: None,
|
||||
enable_nsfw: None,
|
||||
community_creation_admin_only: None,
|
||||
require_email_verification: None,
|
||||
require_application: Some(false),
|
||||
application_question: None,
|
||||
private_instance: None,
|
||||
default_theme: None,
|
||||
legal_information: None,
|
||||
default_post_listing_type: None,
|
||||
auth: "",
|
||||
});
|
||||
editSiteForm.auth = alpha.auth.unwrap();
|
||||
await alpha.client.editSite(editSiteForm);
|
||||
editSiteForm.auth = beta.auth.unwrap();
|
||||
await beta.client.editSite(editSiteForm);
|
||||
editSiteForm.auth = gamma.auth.unwrap();
|
||||
await gamma.client.editSite(editSiteForm);
|
||||
editSiteForm.auth = delta.auth.unwrap();
|
||||
await delta.client.editSite(editSiteForm);
|
||||
editSiteForm.auth = epsilon.auth.unwrap();
|
||||
await epsilon.client.editSite(editSiteForm);
|
||||
|
||||
// Create the main beta community, follow it
|
||||
await createCommunity(beta, "main");
|
||||
|
@ -150,27 +184,30 @@ export async function createPost(
|
|||
community_id: number
|
||||
): Promise<PostResponse> {
|
||||
let name = randomString(5);
|
||||
let body = randomString(10);
|
||||
let url = 'https://google.com/';
|
||||
let form: CreatePost = {
|
||||
let body = Some(randomString(10));
|
||||
let url = Some('https://google.com/');
|
||||
let form = new CreatePost({
|
||||
name,
|
||||
url,
|
||||
body,
|
||||
auth: api.auth,
|
||||
auth: api.auth.unwrap(),
|
||||
community_id,
|
||||
nsfw: false,
|
||||
};
|
||||
nsfw: None,
|
||||
honeypot: None,
|
||||
});
|
||||
return api.client.createPost(form);
|
||||
}
|
||||
|
||||
export async function editPost(api: API, post: Post): Promise<PostResponse> {
|
||||
let name = 'A jest test federated post, updated';
|
||||
let form: EditPost = {
|
||||
let name = Some('A jest test federated post, updated');
|
||||
let form = new EditPost({
|
||||
name,
|
||||
post_id: post.id,
|
||||
auth: api.auth,
|
||||
nsfw: false,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
nsfw: None,
|
||||
url: None,
|
||||
body: None,
|
||||
});
|
||||
return api.client.editPost(form);
|
||||
}
|
||||
|
||||
|
@ -179,11 +216,11 @@ export async function deletePost(
|
|||
deleted: boolean,
|
||||
post: Post
|
||||
): Promise<PostResponse> {
|
||||
let form: DeletePost = {
|
||||
let form = new DeletePost({
|
||||
post_id: post.id,
|
||||
deleted: deleted,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.deletePost(form);
|
||||
}
|
||||
|
||||
|
@ -192,11 +229,12 @@ export async function removePost(
|
|||
removed: boolean,
|
||||
post: Post
|
||||
): Promise<PostResponse> {
|
||||
let form: RemovePost = {
|
||||
let form = new RemovePost({
|
||||
post_id: post.id,
|
||||
removed,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
reason: None,
|
||||
});
|
||||
return api.client.removePost(form);
|
||||
}
|
||||
|
||||
|
@ -205,11 +243,11 @@ export async function stickyPost(
|
|||
stickied: boolean,
|
||||
post: Post
|
||||
): Promise<PostResponse> {
|
||||
let form: StickyPost = {
|
||||
let form = new StickyPost({
|
||||
post_id: post.id,
|
||||
stickied,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.stickyPost(form);
|
||||
}
|
||||
|
||||
|
@ -218,11 +256,11 @@ export async function lockPost(
|
|||
locked: boolean,
|
||||
post: Post
|
||||
): Promise<PostResponse> {
|
||||
let form: LockPost = {
|
||||
let form = new LockPost({
|
||||
post_id: post.id,
|
||||
locked,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.lockPost(form);
|
||||
}
|
||||
|
||||
|
@ -230,9 +268,10 @@ export async function resolvePost(
|
|||
api: API,
|
||||
post: Post
|
||||
): Promise<ResolveObjectResponse> {
|
||||
let form: ResolveObject = {
|
||||
let form = new ResolveObject({
|
||||
q: post.ap_id,
|
||||
};
|
||||
auth: api.auth,
|
||||
});
|
||||
return api.client.resolveObject(form);
|
||||
}
|
||||
|
||||
|
@ -240,11 +279,18 @@ export async function searchPostLocal(
|
|||
api: API,
|
||||
post: Post
|
||||
): Promise<SearchResponse> {
|
||||
let form: Search = {
|
||||
let form = new Search({
|
||||
q: post.name,
|
||||
type_: SearchType.Posts,
|
||||
sort: SortType.TopAll,
|
||||
};
|
||||
type_: Some(SearchType.Posts),
|
||||
sort: Some(SortType.TopAll),
|
||||
community_id: None,
|
||||
community_name: None,
|
||||
creator_id: None,
|
||||
listing_type: None,
|
||||
page: None,
|
||||
limit: None,
|
||||
auth: api.auth,
|
||||
});
|
||||
return api.client.search(form);
|
||||
}
|
||||
|
||||
|
@ -252,19 +298,42 @@ export async function getPost(
|
|||
api: API,
|
||||
post_id: number
|
||||
): Promise<GetPostResponse> {
|
||||
let form: GetPost = {
|
||||
id: post_id,
|
||||
};
|
||||
let form = new GetPost({
|
||||
id: Some(post_id),
|
||||
comment_id: None,
|
||||
auth: api.auth,
|
||||
});
|
||||
return api.client.getPost(form);
|
||||
}
|
||||
|
||||
export async function getComments(
|
||||
api: API,
|
||||
post_id: number
|
||||
): Promise<GetCommentsResponse> {
|
||||
let form = new GetComments({
|
||||
post_id: Some(post_id),
|
||||
type_: Some(ListingType.All),
|
||||
sort: Some(CommentSortType.New), // TODO this sort might be wrong
|
||||
max_depth: None,
|
||||
page: None,
|
||||
limit: None,
|
||||
community_id: None,
|
||||
community_name: None,
|
||||
saved_only: None,
|
||||
parent_id: None,
|
||||
auth: api.auth,
|
||||
});
|
||||
return api.client.getComments(form);
|
||||
}
|
||||
|
||||
export async function resolveComment(
|
||||
api: API,
|
||||
comment: Comment
|
||||
): Promise<ResolveObjectResponse> {
|
||||
let form: ResolveObject = {
|
||||
let form = new ResolveObject({
|
||||
q: comment.ap_id,
|
||||
};
|
||||
auth: api.auth,
|
||||
});
|
||||
return api.client.resolveObject(form);
|
||||
}
|
||||
|
||||
|
@ -272,9 +341,10 @@ export async function resolveBetaCommunity(
|
|||
api: API
|
||||
): Promise<ResolveObjectResponse> {
|
||||
// Use short-hand search url
|
||||
let form: ResolveObject = {
|
||||
let form = new ResolveObject({
|
||||
q: '!main@lemmy-beta:8551',
|
||||
};
|
||||
auth: api.auth,
|
||||
});
|
||||
return api.client.resolveObject(form);
|
||||
}
|
||||
|
||||
|
@ -282,9 +352,10 @@ export async function resolveCommunity(
|
|||
api: API,
|
||||
q: string
|
||||
): Promise<ResolveObjectResponse> {
|
||||
let form: ResolveObject = {
|
||||
let form = new ResolveObject({
|
||||
q,
|
||||
};
|
||||
auth: api.auth,
|
||||
});
|
||||
return api.client.resolveObject(form);
|
||||
}
|
||||
|
||||
|
@ -292,9 +363,10 @@ export async function resolvePerson(
|
|||
api: API,
|
||||
apShortname: string
|
||||
): Promise<ResolveObjectResponse> {
|
||||
let form: ResolveObject = {
|
||||
let form = new ResolveObject({
|
||||
q: apShortname,
|
||||
};
|
||||
auth: api.auth,
|
||||
});
|
||||
return api.client.resolveObject(form);
|
||||
}
|
||||
|
||||
|
@ -305,12 +377,14 @@ export async function banPersonFromSite(
|
|||
remove_data: boolean
|
||||
): Promise<BanPersonResponse> {
|
||||
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
|
||||
let form: BanPerson = {
|
||||
let form = new BanPerson({
|
||||
person_id,
|
||||
ban,
|
||||
remove_data,
|
||||
auth: api.auth,
|
||||
};
|
||||
remove_data: Some(remove_data),
|
||||
auth: api.auth.unwrap(),
|
||||
reason: None,
|
||||
expires: None,
|
||||
});
|
||||
return api.client.banPerson(form);
|
||||
}
|
||||
|
||||
|
@ -321,13 +395,15 @@ export async function banPersonFromCommunity(
|
|||
remove_data: boolean,
|
||||
ban: boolean
|
||||
): Promise<BanFromCommunityResponse> {
|
||||
let form: BanFromCommunity = {
|
||||
let form = new BanFromCommunity({
|
||||
person_id,
|
||||
community_id,
|
||||
remove_data,
|
||||
remove_data: Some(remove_data),
|
||||
ban,
|
||||
auth: api.auth,
|
||||
};
|
||||
reason: None,
|
||||
expires: None,
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.banFromCommunity(form);
|
||||
}
|
||||
|
||||
|
@ -336,11 +412,11 @@ export async function followCommunity(
|
|||
follow: boolean,
|
||||
community_id: number
|
||||
): Promise<CommunityResponse> {
|
||||
let form: FollowCommunity = {
|
||||
let form = new FollowCommunity({
|
||||
community_id,
|
||||
follow,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap()
|
||||
});
|
||||
return api.client.followCommunity(form);
|
||||
}
|
||||
|
||||
|
@ -349,11 +425,11 @@ export async function likePost(
|
|||
score: number,
|
||||
post: Post
|
||||
): Promise<PostResponse> {
|
||||
let form: CreatePostLike = {
|
||||
let form = new CreatePostLike({
|
||||
post_id: post.id,
|
||||
score: score,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap()
|
||||
});
|
||||
|
||||
return api.client.likePost(form);
|
||||
}
|
||||
|
@ -361,15 +437,16 @@ export async function likePost(
|
|||
export async function createComment(
|
||||
api: API,
|
||||
post_id: number,
|
||||
parent_id?: number,
|
||||
parent_id: Option<number>,
|
||||
content = 'a jest test comment'
|
||||
): Promise<CommentResponse> {
|
||||
let form: CreateComment = {
|
||||
let form = new CreateComment({
|
||||
content,
|
||||
post_id,
|
||||
parent_id,
|
||||
auth: api.auth,
|
||||
};
|
||||
form_id: None,
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.createComment(form);
|
||||
}
|
||||
|
||||
|
@ -378,11 +455,12 @@ export async function editComment(
|
|||
comment_id: number,
|
||||
content = 'A jest test federated comment update'
|
||||
): Promise<CommentResponse> {
|
||||
let form: EditComment = {
|
||||
let form = new EditComment({
|
||||
content,
|
||||
comment_id,
|
||||
auth: api.auth,
|
||||
};
|
||||
form_id: None,
|
||||
auth: api.auth.unwrap()
|
||||
});
|
||||
return api.client.editComment(form);
|
||||
}
|
||||
|
||||
|
@ -391,11 +469,11 @@ export async function deleteComment(
|
|||
deleted: boolean,
|
||||
comment_id: number
|
||||
): Promise<CommentResponse> {
|
||||
let form: DeleteComment = {
|
||||
let form = new DeleteComment({
|
||||
comment_id,
|
||||
deleted,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.deleteComment(form);
|
||||
}
|
||||
|
||||
|
@ -404,20 +482,23 @@ export async function removeComment(
|
|||
removed: boolean,
|
||||
comment_id: number
|
||||
): Promise<CommentResponse> {
|
||||
let form: RemoveComment = {
|
||||
let form = new RemoveComment({
|
||||
comment_id,
|
||||
removed,
|
||||
auth: api.auth,
|
||||
};
|
||||
reason: None,
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.removeComment(form);
|
||||
}
|
||||
|
||||
export async function getMentions(api: API): Promise<GetPersonMentionsResponse> {
|
||||
let form: GetPersonMentions = {
|
||||
sort: SortType.New,
|
||||
unread_only: false,
|
||||
auth: api.auth,
|
||||
};
|
||||
let form = new GetPersonMentions({
|
||||
sort: Some(CommentSortType.New),
|
||||
unread_only: Some(false),
|
||||
auth: api.auth.unwrap(),
|
||||
page: None,
|
||||
limit: None,
|
||||
});
|
||||
return api.client.getPersonMentions(form);
|
||||
}
|
||||
|
||||
|
@ -426,11 +507,11 @@ export async function likeComment(
|
|||
score: number,
|
||||
comment: Comment
|
||||
): Promise<CommentResponse> {
|
||||
let form: CreateCommentLike = {
|
||||
let form = new CreateCommentLike({
|
||||
comment_id: comment.id,
|
||||
score,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.likeComment(form);
|
||||
}
|
||||
|
||||
|
@ -438,14 +519,17 @@ export async function createCommunity(
|
|||
api: API,
|
||||
name_: string = randomString(5)
|
||||
): Promise<CommunityResponse> {
|
||||
let description = 'a sample description';
|
||||
let form: CreateCommunity = {
|
||||
let description = Some('a sample description');
|
||||
let form = new CreateCommunity({
|
||||
name: name_,
|
||||
title: name_,
|
||||
description,
|
||||
nsfw: false,
|
||||
auth: api.auth,
|
||||
};
|
||||
nsfw: None,
|
||||
icon: None,
|
||||
banner: None,
|
||||
posting_restricted_to_mods: None,
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.createCommunity(form);
|
||||
}
|
||||
|
||||
|
@ -453,9 +537,11 @@ export async function getCommunity(
|
|||
api: API,
|
||||
id: number
|
||||
): Promise<CommunityResponse> {
|
||||
let form: GetCommunity = {
|
||||
id,
|
||||
};
|
||||
let form = new GetCommunity({
|
||||
id: Some(id),
|
||||
name: None,
|
||||
auth: api.auth,
|
||||
});
|
||||
return api.client.getCommunity(form);
|
||||
}
|
||||
|
||||
|
@ -464,11 +550,11 @@ export async function deleteCommunity(
|
|||
deleted: boolean,
|
||||
community_id: number
|
||||
): Promise<CommunityResponse> {
|
||||
let form: DeleteCommunity = {
|
||||
let form = new DeleteCommunity({
|
||||
community_id,
|
||||
deleted,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.deleteCommunity(form);
|
||||
}
|
||||
|
||||
|
@ -477,11 +563,13 @@ export async function removeCommunity(
|
|||
removed: boolean,
|
||||
community_id: number
|
||||
): Promise<CommunityResponse> {
|
||||
let form: RemoveCommunity = {
|
||||
let form = new RemoveCommunity({
|
||||
community_id,
|
||||
removed,
|
||||
auth: api.auth,
|
||||
};
|
||||
reason: None,
|
||||
expires: None,
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.removeCommunity(form);
|
||||
}
|
||||
|
||||
|
@ -490,11 +578,11 @@ export async function createPrivateMessage(
|
|||
recipient_id: number
|
||||
): Promise<PrivateMessageResponse> {
|
||||
let content = 'A jest test federated private message';
|
||||
let form: CreatePrivateMessage = {
|
||||
let form = new CreatePrivateMessage({
|
||||
content,
|
||||
recipient_id,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.createPrivateMessage(form);
|
||||
}
|
||||
|
||||
|
@ -503,11 +591,11 @@ export async function editPrivateMessage(
|
|||
private_message_id: number
|
||||
): Promise<PrivateMessageResponse> {
|
||||
let updatedContent = 'A jest test federated private message edited';
|
||||
let form: EditPrivateMessage = {
|
||||
let form = new EditPrivateMessage({
|
||||
content: updatedContent,
|
||||
private_message_id,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.editPrivateMessage(form);
|
||||
}
|
||||
|
||||
|
@ -516,11 +604,11 @@ export async function deletePrivateMessage(
|
|||
deleted: boolean,
|
||||
private_message_id: number
|
||||
): Promise<PrivateMessageResponse> {
|
||||
let form: DeletePrivateMessage = {
|
||||
let form = new DeletePrivateMessage({
|
||||
deleted,
|
||||
private_message_id,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.deletePrivateMessage(form);
|
||||
}
|
||||
|
||||
|
@ -528,32 +616,77 @@ export async function registerUser(
|
|||
api: API,
|
||||
username: string = randomString(5)
|
||||
): Promise<LoginResponse> {
|
||||
let form: Register = {
|
||||
let form = new Register({
|
||||
username,
|
||||
password,
|
||||
password_verify: password,
|
||||
show_nsfw: true,
|
||||
};
|
||||
email: None,
|
||||
captcha_uuid: None,
|
||||
captcha_answer: None,
|
||||
honeypot: None,
|
||||
answer: None,
|
||||
});
|
||||
return api.client.register(form);
|
||||
}
|
||||
|
||||
export async function saveUserSettingsBio(
|
||||
api: API
|
||||
): Promise<LoginResponse> {
|
||||
let form: SaveUserSettings = {
|
||||
show_nsfw: true,
|
||||
theme: 'darkly',
|
||||
default_sort_type: Object.keys(SortType).indexOf(SortType.Active),
|
||||
default_listing_type: Object.keys(ListingType).indexOf(ListingType.All),
|
||||
lang: 'en',
|
||||
show_avatars: true,
|
||||
send_notifications_to_email: false,
|
||||
bio: 'a changed bio',
|
||||
auth: api.auth,
|
||||
};
|
||||
let form = new SaveUserSettings({
|
||||
show_nsfw: Some(true),
|
||||
theme: Some('darkly'),
|
||||
default_sort_type: Some(Object.keys(SortType).indexOf(SortType.Active)),
|
||||
default_listing_type: Some(Object.keys(ListingType).indexOf(ListingType.All)),
|
||||
lang: Some('en'),
|
||||
show_avatars: Some(true),
|
||||
send_notifications_to_email: Some(false),
|
||||
bio: Some('a changed bio'),
|
||||
avatar: None,
|
||||
banner: None,
|
||||
display_name: None,
|
||||
email: None,
|
||||
matrix_user_id: None,
|
||||
show_scores: None,
|
||||
show_read_posts: None,
|
||||
show_bot_accounts: None,
|
||||
show_new_post_notifs: None,
|
||||
bot_account: None,
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return saveUserSettings(api, form);
|
||||
}
|
||||
|
||||
export async function saveUserSettingsFederated(
|
||||
api: API
|
||||
): Promise<LoginResponse> {
|
||||
let avatar = Some('https://image.flaticon.com/icons/png/512/35/35896.png');
|
||||
let banner = Some('https://image.flaticon.com/icons/png/512/36/35896.png');
|
||||
let bio = Some('a changed bio');
|
||||
let form = new SaveUserSettings({
|
||||
show_nsfw: Some(false),
|
||||
theme: Some(''),
|
||||
default_sort_type: Some(Object.keys(SortType).indexOf(SortType.Hot)),
|
||||
default_listing_type: Some(Object.keys(ListingType).indexOf(ListingType.All)),
|
||||
lang: Some(''),
|
||||
avatar,
|
||||
banner,
|
||||
display_name: Some('user321'),
|
||||
show_avatars: Some(false),
|
||||
send_notifications_to_email: Some(false),
|
||||
bio,
|
||||
email: None,
|
||||
show_scores: None,
|
||||
show_read_posts: None,
|
||||
matrix_user_id: None,
|
||||
bot_account: None,
|
||||
show_bot_accounts: None,
|
||||
show_new_post_notifs: None,
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return await saveUserSettings(alpha, form);
|
||||
}
|
||||
|
||||
export async function saveUserSettings(
|
||||
api: API,
|
||||
form: SaveUserSettings
|
||||
|
@ -564,29 +697,31 @@ export async function saveUserSettings(
|
|||
export async function deleteUser(
|
||||
api: API
|
||||
): Promise<DeleteAccountResponse> {
|
||||
let form: DeleteAccount = {
|
||||
auth: api.auth,
|
||||
let form = new DeleteAccount({
|
||||
auth: api.auth.unwrap(),
|
||||
password
|
||||
};
|
||||
});
|
||||
return api.client.deleteAccount(form);
|
||||
}
|
||||
|
||||
export async function getSite(
|
||||
api: API
|
||||
): Promise<GetSiteResponse> {
|
||||
let form: GetSite = {
|
||||
let form = new GetSite({
|
||||
auth: api.auth,
|
||||
};
|
||||
});
|
||||
return api.client.getSite(form);
|
||||
}
|
||||
|
||||
export async function listPrivateMessages(
|
||||
api: API
|
||||
): Promise<PrivateMessagesResponse> {
|
||||
let form: GetPrivateMessages = {
|
||||
auth: api.auth,
|
||||
unread_only: false,
|
||||
};
|
||||
let form = new GetPrivateMessages({
|
||||
auth: api.auth.unwrap(),
|
||||
unread_only: Some(false),
|
||||
page: None,
|
||||
limit: None,
|
||||
});
|
||||
return api.client.getPrivateMessages(form);
|
||||
}
|
||||
|
||||
|
@ -595,7 +730,7 @@ export async function unfollowRemotes(
|
|||
): Promise<GetSiteResponse> {
|
||||
// Unfollow all remote communities
|
||||
let site = await getSite(api);
|
||||
let remoteFollowed = site.my_user.follows.filter(
|
||||
let remoteFollowed = site.my_user.unwrap().follows.filter(
|
||||
c => c.community.local == false
|
||||
);
|
||||
for (let cu of remoteFollowed) {
|
||||
|
@ -607,9 +742,11 @@ export async function unfollowRemotes(
|
|||
|
||||
export async function followBeta(api: API): Promise<CommunityResponse> {
|
||||
let betaCommunity = (await resolveBetaCommunity(api)).community;
|
||||
if (betaCommunity) {
|
||||
let follow = await followCommunity(api, true, betaCommunity.community.id);
|
||||
if (betaCommunity.isSome()) {
|
||||
let follow = await followCommunity(api, true, betaCommunity.unwrap().community.id);
|
||||
return follow;
|
||||
} else {
|
||||
return Promise.reject("no community worked");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -618,18 +755,22 @@ export async function reportPost(
|
|||
post_id: number,
|
||||
reason: string
|
||||
): Promise<PostReportResponse> {
|
||||
let form: CreatePostReport = {
|
||||
let form = new CreatePostReport({
|
||||
post_id,
|
||||
reason,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.createPostReport(form);
|
||||
}
|
||||
|
||||
export async function listPostReports(api: API): Promise<ListPostReportsResponse> {
|
||||
let form: ListPostReports = {
|
||||
auth: api.auth,
|
||||
};
|
||||
let form = new ListPostReports({
|
||||
auth: api.auth.unwrap(),
|
||||
page: None,
|
||||
limit: None,
|
||||
community_id: None,
|
||||
unresolved_only: None,
|
||||
});
|
||||
return api.client.listPostReports(form);
|
||||
}
|
||||
|
||||
|
@ -638,18 +779,22 @@ export async function reportComment(
|
|||
comment_id: number,
|
||||
reason: string
|
||||
): Promise<CommentReportResponse> {
|
||||
let form: CreateCommentReport = {
|
||||
let form = new CreateCommentReport({
|
||||
comment_id,
|
||||
reason,
|
||||
auth: api.auth,
|
||||
};
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.createCommentReport(form);
|
||||
}
|
||||
|
||||
export async function listCommentReports(api: API): Promise<ListCommentReportsResponse> {
|
||||
let form: ListCommentReports = {
|
||||
auth: api.auth,
|
||||
};
|
||||
let form = new ListCommentReports({
|
||||
page: None,
|
||||
limit: None,
|
||||
community_id: None,
|
||||
unresolved_only: None,
|
||||
auth: api.auth.unwrap(),
|
||||
});
|
||||
return api.client.listCommentReports(form);
|
||||
}
|
||||
|
||||
|
@ -681,3 +826,15 @@ export async function unfollows() {
|
|||
await unfollowRemotes(delta);
|
||||
await unfollowRemotes(epsilon);
|
||||
}
|
||||
|
||||
export function getCommentParentId(comment: Comment): Option<number> {
|
||||
let split = comment.path.split(".");
|
||||
// remove the 0
|
||||
split.shift();
|
||||
|
||||
if (split.length > 1) {
|
||||
return Some(Number(split[split.length - 2]));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
jest.setTimeout(120000);
|
||||
import {None} from '@sniptt/monads';
|
||||
import {
|
||||
PersonViewSafe,
|
||||
} from 'lemmy-js-client';
|
||||
|
||||
import {
|
||||
alpha,
|
||||
beta,
|
||||
registerUser,
|
||||
resolvePerson,
|
||||
saveUserSettings,
|
||||
getSite,
|
||||
createPost,
|
||||
resolveCommunity,
|
||||
|
@ -14,23 +18,18 @@ import {
|
|||
resolvePost,
|
||||
API,
|
||||
resolveComment,
|
||||
saveUserSettingsFederated,
|
||||
} from './shared';
|
||||
import {
|
||||
PersonViewSafe,
|
||||
SaveUserSettings,
|
||||
SortType,
|
||||
ListingType,
|
||||
} from 'lemmy-js-client';
|
||||
|
||||
let apShortname: string;
|
||||
|
||||
function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe) {
|
||||
expect(userOne.person.name).toBe(userTwo.person.name);
|
||||
expect(userOne.person.display_name).toBe(userTwo.person.display_name);
|
||||
expect(userOne.person.bio).toBe(userTwo.person.bio);
|
||||
expect(userOne.person.display_name.unwrapOr("none")).toBe(userTwo.person.display_name.unwrapOr("none"));
|
||||
expect(userOne.person.bio.unwrapOr("none")).toBe(userTwo.person.bio.unwrapOr("none"));
|
||||
expect(userOne.person.actor_id).toBe(userTwo.person.actor_id);
|
||||
expect(userOne.person.avatar).toBe(userTwo.person.avatar);
|
||||
expect(userOne.person.banner).toBe(userTwo.person.banner);
|
||||
expect(userOne.person.avatar.unwrapOr("none")).toBe(userTwo.person.avatar.unwrapOr("none"));
|
||||
expect(userOne.person.banner.unwrapOr("none")).toBe(userTwo.person.banner.unwrapOr("none"));
|
||||
expect(userOne.person.published).toBe(userTwo.person.published);
|
||||
}
|
||||
|
||||
|
@ -41,31 +40,13 @@ test('Create user', async () => {
|
|||
|
||||
let site = await getSite(alpha);
|
||||
expect(site.my_user).toBeDefined();
|
||||
apShortname = `@${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
|
||||
apShortname = `@${site.my_user.unwrap().local_user_view.person.name}@lemmy-alpha:8541`;
|
||||
});
|
||||
|
||||
test('Set some user settings, check that they are federated', async () => {
|
||||
let avatar = 'https://image.flaticon.com/icons/png/512/35/35896.png';
|
||||
let banner = 'https://image.flaticon.com/icons/png/512/36/35896.png';
|
||||
let bio = 'a changed bio';
|
||||
let form: SaveUserSettings = {
|
||||
show_nsfw: false,
|
||||
theme: '',
|
||||
default_sort_type: Object.keys(SortType).indexOf(SortType.Hot),
|
||||
default_listing_type: Object.keys(ListingType).indexOf(ListingType.All),
|
||||
lang: '',
|
||||
avatar,
|
||||
banner,
|
||||
display_name: 'user321',
|
||||
show_avatars: false,
|
||||
send_notifications_to_email: false,
|
||||
bio,
|
||||
auth: alpha.auth,
|
||||
};
|
||||
await saveUserSettings(alpha, form);
|
||||
|
||||
let alphaPerson = (await resolvePerson(alpha, apShortname)).person;
|
||||
let betaPerson = (await resolvePerson(beta, apShortname)).person;
|
||||
await saveUserSettingsFederated(alpha);
|
||||
let alphaPerson = (await resolvePerson(alpha, apShortname)).person.unwrap();
|
||||
let betaPerson = (await resolvePerson(beta, apShortname)).person.unwrap();
|
||||
assertUserFederation(alphaPerson, betaPerson);
|
||||
});
|
||||
|
||||
|
@ -78,23 +59,23 @@ test('Delete user', async () => {
|
|||
}
|
||||
|
||||
// make a local post and comment
|
||||
let alphaCommunity = (await resolveCommunity(user, '!main@lemmy-alpha:8541')).community;
|
||||
let alphaCommunity = (await resolveCommunity(user, '!main@lemmy-alpha:8541')).community.unwrap();
|
||||
let localPost = (await createPost(user, alphaCommunity.community.id)).post_view.post;
|
||||
expect(localPost).toBeDefined();
|
||||
let localComment = (await createComment(user, localPost.id)).comment_view.comment;
|
||||
let localComment = (await createComment(user, localPost.id, None)).comment_view.comment;
|
||||
expect(localComment).toBeDefined();
|
||||
|
||||
// make a remote post and comment
|
||||
let betaCommunity = (await resolveBetaCommunity(user)).community;
|
||||
let betaCommunity = (await resolveBetaCommunity(user)).community.unwrap();
|
||||
let remotePost = (await createPost(user, betaCommunity.community.id)).post_view.post;
|
||||
expect(remotePost).toBeDefined();
|
||||
let remoteComment = (await createComment(user, remotePost.id)).comment_view.comment;
|
||||
let remoteComment = (await createComment(user, remotePost.id, None)).comment_view.comment;
|
||||
expect(remoteComment).toBeDefined();
|
||||
|
||||
await deleteUser(user);
|
||||
|
||||
expect((await resolvePost(alpha, localPost)).post).toBeUndefined();
|
||||
expect((await resolveComment(alpha, localComment)).comment).toBeUndefined();
|
||||
expect((await resolvePost(alpha, remotePost)).post).toBeUndefined();
|
||||
expect((await resolveComment(alpha, remoteComment)).comment).toBeUndefined();
|
||||
expect((await resolvePost(alpha, localPost)).post.isNone()).toBe(true);
|
||||
expect((await resolveComment(alpha, localComment)).comment.isNone()).toBe(true)
|
||||
expect((await resolvePost(alpha, remotePost)).post.isNone()).toBe(true)
|
||||
expect((await resolveComment(alpha, remoteComment)).comment.isNone()).toBe(true)
|
||||
});
|
||||
|
|
3484
api_tests/yarn.lock
3484
api_tests/yarn.lock
File diff suppressed because it is too large
Load diff
|
@ -13,7 +13,10 @@ use lemmy_apub::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::LocalUserId,
|
||||
source::comment::{CommentLike, CommentLikeForm},
|
||||
source::{
|
||||
comment::{CommentLike, CommentLikeForm},
|
||||
comment_reply::CommentReply,
|
||||
},
|
||||
traits::Likeable,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||
|
@ -53,14 +56,20 @@ impl Perform for CreateCommentLike {
|
|||
)
|
||||
.await?;
|
||||
|
||||
// Add parent user to recipients
|
||||
let recipient_id = orig_comment.get_recipient_id();
|
||||
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read_person(conn, recipient_id)
|
||||
// Add parent poster or commenter to recipients
|
||||
let comment_reply = blocking(context.pool(), move |conn| {
|
||||
CommentReply::read_by_comment(conn, comment_id)
|
||||
})
|
||||
.await?
|
||||
{
|
||||
recipient_ids.push(local_recipient.local_user.id);
|
||||
.await?;
|
||||
if let Ok(reply) = comment_reply {
|
||||
let recipient_id = reply.recipient_id;
|
||||
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read_person(conn, recipient_id)
|
||||
})
|
||||
.await?
|
||||
{
|
||||
recipient_ids.push(local_recipient.local_user.id);
|
||||
}
|
||||
}
|
||||
|
||||
let like_form = CommentLikeForm {
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
comment::{CommentResponse, MarkCommentAsRead},
|
||||
utils::{blocking, get_local_user_view_from_jwt},
|
||||
};
|
||||
use lemmy_db_schema::source::comment::Comment;
|
||||
use lemmy_db_views::structs::CommentView;
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkCommentAsRead {
|
||||
type Response = CommentResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
let data: &MarkCommentAsRead = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, None)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Verify that only the recipient can mark as read
|
||||
if local_user_view.person.id != orig_comment.get_recipient_id() {
|
||||
return Err(LemmyError::from_message("no_comment_edit_allowed"));
|
||||
}
|
||||
|
||||
// Do the mark as read
|
||||
let read = data.read;
|
||||
blocking(context.pool(), move |conn| {
|
||||
Comment::update_read(conn, comment_id, read)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
// Refetch it
|
||||
let comment_id = data.comment_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids: Vec::new(),
|
||||
form_id: None,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
mod like;
|
||||
mod mark_as_read;
|
||||
mod save;
|
||||
|
|
|
@ -60,6 +60,9 @@ pub async fn match_websocket_operation(
|
|||
UserOperation::MarkPersonMentionAsRead => {
|
||||
do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::MarkCommentReplyAsRead => {
|
||||
do_websocket_operation::<MarkCommentReplyAsRead>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::MarkAllAsRead => {
|
||||
do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
|
||||
}
|
||||
|
@ -155,9 +158,6 @@ pub async fn match_websocket_operation(
|
|||
}
|
||||
|
||||
// Comment ops
|
||||
UserOperation::MarkCommentAsRead => {
|
||||
do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::SaveComment => {
|
||||
do_websocket_operation::<SaveComment>(context, id, op, data).await
|
||||
}
|
||||
|
|
|
@ -27,12 +27,15 @@ impl Perform for GetPersonMentions {
|
|||
let limit = data.limit;
|
||||
let unread_only = data.unread_only;
|
||||
let person_id = local_user_view.person.id;
|
||||
let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||
|
||||
let mentions = blocking(context.pool(), move |conn| {
|
||||
PersonMentionQueryBuilder::create(conn)
|
||||
.recipient_id(person_id)
|
||||
.my_person_id(person_id)
|
||||
.sort(sort)
|
||||
.unread_only(unread_only)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
|||
person::{GetReplies, GetRepliesResponse},
|
||||
utils::{blocking, get_local_user_view_from_jwt},
|
||||
};
|
||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||
use lemmy_db_views_actor::comment_reply_view::CommentReplyQueryBuilder;
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
|
@ -30,12 +30,12 @@ impl Perform for GetReplies {
|
|||
let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||
|
||||
let replies = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
CommentReplyQueryBuilder::create(conn)
|
||||
.recipient_id(person_id)
|
||||
.my_person_id(person_id)
|
||||
.sort(sort)
|
||||
.unread_only(unread_only)
|
||||
.recipient_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.my_person_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
|
|
|
@ -5,11 +5,10 @@ use lemmy_api_common::{
|
|||
utils::{blocking, get_local_user_view_from_jwt},
|
||||
};
|
||||
use lemmy_db_schema::source::{
|
||||
comment::Comment,
|
||||
comment_reply::CommentReply,
|
||||
person_mention::PersonMention,
|
||||
private_message::PrivateMessage,
|
||||
};
|
||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
|
@ -26,42 +25,28 @@ impl Perform for MarkAllAsRead {
|
|||
let data: &MarkAllAsRead = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let replies = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.my_person_id(person_id)
|
||||
.recipient_id(person_id)
|
||||
.unread_only(true)
|
||||
.page(1)
|
||||
.limit(std::i64::MAX)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
// TODO: this should probably be a bulk operation
|
||||
// Not easy to do as a bulk operation,
|
||||
// because recipient_id isn't in the comment table
|
||||
for comment_view in &replies {
|
||||
let reply_id = comment_view.comment.id;
|
||||
let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
|
||||
blocking(context.pool(), mark_as_read)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
}
|
||||
// Mark all comment_replies as read
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommentReply::mark_all_as_read(conn, person_id)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
// Mark all user mentions as read
|
||||
let update_person_mentions =
|
||||
move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
|
||||
blocking(context.pool(), update_person_mentions)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
blocking(context.pool(), move |conn| {
|
||||
PersonMention::mark_all_as_read(conn, person_id)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
// Mark all private_messages as read
|
||||
let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
|
||||
blocking(context.pool(), update_pm)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
||||
blocking(context.pool(), move |conn| {
|
||||
PrivateMessage::mark_all_as_read(conn, person_id)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
||||
|
||||
Ok(GetRepliesResponse { replies: vec![] })
|
||||
}
|
||||
|
|
52
crates/api/src/local_user/notifications/mark_reply_read.rs
Normal file
52
crates/api/src/local_user/notifications/mark_reply_read.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
person::{CommentReplyResponse, MarkCommentReplyAsRead},
|
||||
utils::{blocking, get_local_user_view_from_jwt},
|
||||
};
|
||||
use lemmy_db_schema::{source::comment_reply::CommentReply, traits::Crud};
|
||||
use lemmy_db_views_actor::structs::CommentReplyView;
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for MarkCommentReplyAsRead {
|
||||
type Response = CommentReplyResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<CommentReplyResponse, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let comment_reply_id = data.comment_reply_id;
|
||||
let read_comment_reply = blocking(context.pool(), move |conn| {
|
||||
CommentReply::read(conn, comment_reply_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if local_user_view.person.id != read_comment_reply.recipient_id {
|
||||
return Err(LemmyError::from_message("couldnt_update_comment"));
|
||||
}
|
||||
|
||||
let comment_reply_id = read_comment_reply.id;
|
||||
let read = data.read;
|
||||
let update_reply = move |conn: &'_ _| CommentReply::update_read(conn, comment_reply_id, read);
|
||||
blocking(context.pool(), update_reply)
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
|
||||
let comment_reply_id = read_comment_reply.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_reply_view = blocking(context.pool(), move |conn| {
|
||||
CommentReplyView::read(conn, comment_reply_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(CommentReplyResponse { comment_reply_view })
|
||||
}
|
||||
}
|
|
@ -2,4 +2,5 @@ mod list_mentions;
|
|||
mod list_replies;
|
||||
mod mark_all_read;
|
||||
mod mark_mention_read;
|
||||
mod mark_reply_read;
|
||||
mod unread_count;
|
||||
|
|
|
@ -4,8 +4,8 @@ use lemmy_api_common::{
|
|||
person::{GetUnreadCount, GetUnreadCountResponse},
|
||||
utils::{blocking, get_local_user_view_from_jwt},
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, PrivateMessageView};
|
||||
use lemmy_db_views_actor::structs::PersonMentionView;
|
||||
use lemmy_db_views::structs::PrivateMessageView;
|
||||
use lemmy_db_views_actor::structs::{CommentReplyView, PersonMentionView};
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
|
@ -26,7 +26,7 @@ impl Perform for GetUnreadCount {
|
|||
let person_id = local_user_view.person.id;
|
||||
|
||||
let replies = blocking(context.pool(), move |conn| {
|
||||
CommentView::get_unread_replies(conn, person_id)
|
||||
CommentReplyView::get_unread_replies(conn, person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
|
|
@ -5,7 +5,12 @@ use lemmy_api_common::{
|
|||
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
|
||||
};
|
||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
||||
use lemmy_db_schema::{source::community::Community, traits::DeleteableOrRemoveable, SearchType};
|
||||
use lemmy_db_schema::{
|
||||
source::community::Community,
|
||||
traits::DeleteableOrRemoveable,
|
||||
utils::post_to_comment_sort_type,
|
||||
SearchType,
|
||||
};
|
||||
use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
|
||||
use lemmy_db_views_actor::{
|
||||
community_view::CommunityQueryBuilder,
|
||||
|
@ -88,7 +93,7 @@ impl Perform for Search {
|
|||
SearchType::Comments => {
|
||||
comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.sort(sort.map(post_to_comment_sort_type))
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
|
@ -155,7 +160,7 @@ impl Perform for Search {
|
|||
|
||||
comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.sort(sort)
|
||||
.sort(sort.map(post_to_comment_sort_type))
|
||||
.listing_type(listing_type)
|
||||
.search_term(q)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::sensitive::Sensitive;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommentId, CommentReportId, CommunityId, LocalUserId, PostId},
|
||||
CommentSortType,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentReportView, CommentView};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -45,13 +45,6 @@ pub struct RemoveComment {
|
|||
pub auth: Sensitive<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct MarkCommentAsRead {
|
||||
pub comment_id: CommentId,
|
||||
pub read: bool,
|
||||
pub auth: Sensitive<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct SaveComment {
|
||||
pub comment_id: CommentId,
|
||||
|
@ -76,11 +69,14 @@ pub struct CreateCommentLike {
|
|||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct GetComments {
|
||||
pub type_: Option<ListingType>,
|
||||
pub sort: Option<SortType>,
|
||||
pub sort: Option<CommentSortType>,
|
||||
pub max_depth: Option<i32>,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub community_id: Option<CommunityId>,
|
||||
pub community_name: Option<String>,
|
||||
pub post_id: Option<PostId>,
|
||||
pub parent_id: Option<CommentId>,
|
||||
pub saved_only: Option<bool>,
|
||||
pub auth: Option<Sensitive<String>>,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use crate::sensitive::Sensitive;
|
||||
use lemmy_db_views::structs::{CommentView, PostView, PrivateMessageView};
|
||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonMentionView, PersonViewSafe};
|
||||
use lemmy_db_views_actor::structs::{
|
||||
CommentReplyView,
|
||||
CommunityModeratorView,
|
||||
PersonMentionView,
|
||||
PersonViewSafe,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
|
@ -9,7 +14,8 @@ pub struct Login {
|
|||
pub password: Sensitive<String>,
|
||||
}
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommunityId, PersonId, PersonMentionId, PrivateMessageId},
|
||||
newtypes::{CommentReplyId, CommunityId, PersonId, PersonMentionId, PrivateMessageId},
|
||||
CommentSortType,
|
||||
SortType,
|
||||
};
|
||||
|
||||
|
@ -105,7 +111,7 @@ pub struct GetPersonDetailsResponse {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct GetRepliesResponse {
|
||||
pub replies: Vec<CommentView>,
|
||||
pub replies: Vec<CommentReplyView>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
@ -171,7 +177,7 @@ pub struct BlockPersonResponse {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct GetReplies {
|
||||
pub sort: Option<SortType>,
|
||||
pub sort: Option<CommentSortType>,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub unread_only: Option<bool>,
|
||||
|
@ -180,7 +186,7 @@ pub struct GetReplies {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct GetPersonMentions {
|
||||
pub sort: Option<SortType>,
|
||||
pub sort: Option<CommentSortType>,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub unread_only: Option<bool>,
|
||||
|
@ -199,6 +205,18 @@ pub struct PersonMentionResponse {
|
|||
pub person_mention_view: PersonMentionView,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct MarkCommentReplyAsRead {
|
||||
pub comment_reply_id: CommentReplyId,
|
||||
pub read: bool,
|
||||
pub auth: Sensitive<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CommentReplyResponse {
|
||||
pub comment_reply_view: CommentReplyView,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct DeleteAccount {
|
||||
pub password: Sensitive<String>,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::sensitive::Sensitive;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommunityId, DbUrl, PostId, PostReportId},
|
||||
newtypes::{CommentId, CommunityId, DbUrl, PostId, PostReportId},
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, PostReportView, PostView};
|
||||
use lemmy_db_views::structs::{PostReportView, PostView};
|
||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
@ -27,7 +27,8 @@ pub struct PostResponse {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct GetPost {
|
||||
pub id: PostId,
|
||||
pub id: Option<PostId>,
|
||||
pub comment_id: Option<CommentId>,
|
||||
pub auth: Option<Sensitive<String>>,
|
||||
}
|
||||
|
||||
|
@ -35,7 +36,6 @@ pub struct GetPost {
|
|||
pub struct GetPostResponse {
|
||||
pub post_view: PostView,
|
||||
pub community_view: CommunityView,
|
||||
pub comments: Vec<CommentView>,
|
||||
pub moderators: Vec<CommunityModeratorView>,
|
||||
pub online: usize,
|
||||
}
|
||||
|
|
|
@ -20,11 +20,11 @@ use lemmy_apub::{
|
|||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
|
||||
comment_reply::CommentReply,
|
||||
person_mention::PersonMention,
|
||||
},
|
||||
traits::{Crud, Likeable},
|
||||
};
|
||||
use lemmy_db_views::structs::CommentView;
|
||||
use lemmy_utils::{
|
||||
error::LemmyError,
|
||||
utils::{remove_slurs, scrape_text_for_mentions},
|
||||
|
@ -67,14 +67,18 @@ impl PerformCrud for CreateComment {
|
|||
return Err(LemmyError::from_message("locked"));
|
||||
}
|
||||
|
||||
// If there's a parent_id, check to make sure that comment is in that post
|
||||
if let Some(parent_id) = data.parent_id {
|
||||
// Make sure the parent comment exists
|
||||
let parent = blocking(context.pool(), move |conn| Comment::read(conn, parent_id))
|
||||
// Fetch the parent, if it exists
|
||||
let parent_opt = if let Some(parent_id) = data.parent_id {
|
||||
blocking(context.pool(), move |conn| Comment::read(conn, parent_id))
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_comment"))?;
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Strange issue where sometimes the post ID is incorrect
|
||||
// If there's a parent_id, check to make sure that comment is in that post
|
||||
// Strange issue where sometimes the post ID of the parent comment is incorrect
|
||||
if let Some(parent) = parent_opt.as_ref() {
|
||||
if parent.post_id != post_id {
|
||||
return Err(LemmyError::from_message("couldnt_create_comment"));
|
||||
}
|
||||
|
@ -82,7 +86,6 @@ impl PerformCrud for CreateComment {
|
|||
|
||||
let comment_form = CommentForm {
|
||||
content: content_slurs_removed,
|
||||
parent_id: data.parent_id.to_owned(),
|
||||
post_id: data.post_id,
|
||||
creator_id: local_user_view.person.id,
|
||||
..CommentForm::default()
|
||||
|
@ -90,8 +93,9 @@ impl PerformCrud for CreateComment {
|
|||
|
||||
// Create the comment
|
||||
let comment_form2 = comment_form.clone();
|
||||
let parent_path = parent_opt.to_owned().map(|t| t.path);
|
||||
let inserted_comment = blocking(context.pool(), move |conn| {
|
||||
Comment::create(conn, &comment_form2)
|
||||
Comment::create(conn, &comment_form2, parent_path.as_ref())
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_comment"))?;
|
||||
|
@ -148,35 +152,21 @@ impl PerformCrud for CreateComment {
|
|||
)
|
||||
.await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_id = inserted_comment.id;
|
||||
let comment_view = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, comment_id, Some(person_id))
|
||||
})
|
||||
.await??;
|
||||
|
||||
// If its a comment to yourself, mark it as read
|
||||
if local_user_view.person.id == comment_view.get_recipient_id() {
|
||||
let comment_id = inserted_comment.id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
Comment::update_read(conn, comment_id, true)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||
}
|
||||
// If its a reply, mark the parent as read
|
||||
if let Some(parent_id) = data.parent_id {
|
||||
let parent_comment = blocking(context.pool(), move |conn| {
|
||||
CommentView::read(conn, parent_id, Some(person_id))
|
||||
if let Some(parent) = parent_opt {
|
||||
let parent_id = parent.id;
|
||||
let comment_reply = blocking(context.pool(), move |conn| {
|
||||
CommentReply::read_by_comment(conn, parent_id)
|
||||
})
|
||||
.await??;
|
||||
if local_user_view.person.id == parent_comment.get_recipient_id() {
|
||||
.await?;
|
||||
if let Ok(reply) = comment_reply {
|
||||
blocking(context.pool(), move |conn| {
|
||||
Comment::update_read(conn, parent_id, true)
|
||||
CommentReply::update_read(conn, reply.id, true)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_parent_comment"))?;
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_replies"))?;
|
||||
}
|
||||
|
||||
// If the parent has PersonMentions mark them as read too
|
||||
let person_id = local_user_view.person.id;
|
||||
let person_mention = blocking(context.pool(), move |conn| {
|
||||
|
|
|
@ -10,7 +10,10 @@ use lemmy_api_common::{
|
|||
},
|
||||
};
|
||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
||||
use lemmy_db_schema::{source::community::Community, traits::DeleteableOrRemoveable};
|
||||
use lemmy_db_schema::{
|
||||
source::{comment::Comment, community::Community},
|
||||
traits::{Crud, DeleteableOrRemoveable},
|
||||
};
|
||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
@ -49,16 +52,34 @@ impl PerformCrud for GetComments {
|
|||
None
|
||||
};
|
||||
let sort = data.sort;
|
||||
let max_depth = data.max_depth;
|
||||
let saved_only = data.saved_only;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let parent_id = data.parent_id;
|
||||
|
||||
// If a parent_id is given, fetch the comment to get the path
|
||||
let parent_path = if let Some(parent_id) = parent_id {
|
||||
Some(
|
||||
blocking(context.pool(), move |conn| Comment::read(conn, parent_id))
|
||||
.await??
|
||||
.path,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let post_id = data.post_id;
|
||||
let mut comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.listing_type(listing_type)
|
||||
.sort(sort)
|
||||
.max_depth(max_depth)
|
||||
.saved_only(saved_only)
|
||||
.community_id(community_id)
|
||||
.community_actor_id(community_actor_id)
|
||||
.parent_path(parent_path)
|
||||
.post_id(post_id)
|
||||
.my_person_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.page(page)
|
||||
|
|
|
@ -4,8 +4,11 @@ use lemmy_api_common::{
|
|||
post::{GetPost, GetPostResponse},
|
||||
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt, mark_post_as_read},
|
||||
};
|
||||
use lemmy_db_schema::traits::DeleteableOrRemoveable;
|
||||
use lemmy_db_views::{comment_view::CommentQueryBuilder, structs::PostView};
|
||||
use lemmy_db_schema::{
|
||||
source::comment::Comment,
|
||||
traits::{Crud, DeleteableOrRemoveable},
|
||||
};
|
||||
use lemmy_db_views::structs::PostView;
|
||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::{messages::GetPostUsersOnline, LemmyContext};
|
||||
|
@ -27,35 +30,33 @@ impl PerformCrud for GetPost {
|
|||
|
||||
check_private_instance(&local_user_view, context.pool()).await?;
|
||||
|
||||
let show_bot_accounts = local_user_view
|
||||
.as_ref()
|
||||
.map(|t| t.local_user.show_bot_accounts);
|
||||
let person_id = local_user_view.map(|u| u.person.id);
|
||||
|
||||
let id = data.id;
|
||||
// I'd prefer fetching the post_view by a comment join, but it adds a lot of boilerplate
|
||||
let post_id = if let Some(id) = data.id {
|
||||
id
|
||||
} else if let Some(comment_id) = data.comment_id {
|
||||
blocking(context.pool(), move |conn| Comment::read(conn, comment_id))
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?
|
||||
.post_id
|
||||
} else {
|
||||
Err(LemmyError::from_message("couldnt_find_post"))?
|
||||
};
|
||||
|
||||
let mut post_view = blocking(context.pool(), move |conn| {
|
||||
PostView::read(conn, id, person_id)
|
||||
PostView::read(conn, post_id, person_id)
|
||||
})
|
||||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?;
|
||||
|
||||
// Mark the post as read
|
||||
let post_id = post_view.post.id;
|
||||
if let Some(person_id) = person_id {
|
||||
mark_post_as_read(person_id, id, context.pool()).await?;
|
||||
mark_post_as_read(person_id, post_id, context.pool()).await?;
|
||||
}
|
||||
|
||||
let id = data.id;
|
||||
let mut comments = blocking(context.pool(), move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.my_person_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.post_id(id)
|
||||
.limit(std::i64::MAX)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Necessary for the sidebar
|
||||
// Necessary for the sidebar subscribed
|
||||
let community_id = post_view.community.id;
|
||||
let mut community_view = blocking(context.pool(), move |conn| {
|
||||
CommunityView::read(conn, community_id, person_id)
|
||||
|
@ -69,12 +70,6 @@ impl PerformCrud for GetPost {
|
|||
post_view.post = post_view.post.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
||||
for cv in comments
|
||||
.iter_mut()
|
||||
.filter(|cv| cv.comment.deleted || cv.comment.removed)
|
||||
{
|
||||
cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
if community_view.community.deleted || community_view.community.removed {
|
||||
community_view.community = community_view.community.blank_out_deleted_or_removed_info();
|
||||
}
|
||||
|
@ -87,7 +82,7 @@ impl PerformCrud for GetPost {
|
|||
|
||||
let online = context
|
||||
.chat_server()
|
||||
.send(GetPostUsersOnline { post_id: data.id })
|
||||
.send(GetPostUsersOnline { post_id })
|
||||
.await
|
||||
.unwrap_or(1);
|
||||
|
||||
|
@ -95,7 +90,6 @@ impl PerformCrud for GetPost {
|
|||
Ok(GetPostResponse {
|
||||
post_view,
|
||||
community_view,
|
||||
comments,
|
||||
moderators,
|
||||
online,
|
||||
})
|
||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
|||
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
|
||||
};
|
||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::person::ApubPerson};
|
||||
use lemmy_db_schema::source::person::Person;
|
||||
use lemmy_db_schema::{source::person::Person, utils::post_to_comment_sort_type};
|
||||
use lemmy_db_views::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
|
||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonViewSafe};
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
|
@ -88,7 +88,7 @@ impl PerformCrud for GetPersonDetails {
|
|||
let mut comments_query = CommentQueryBuilder::create(conn)
|
||||
.my_person_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.sort(sort)
|
||||
.sort(sort.map(post_to_comment_sort_type))
|
||||
.saved_only(saved_only)
|
||||
.community_id(community_id)
|
||||
.page(page)
|
||||
|
|
|
@ -104,7 +104,7 @@ async fn get_comment_parent_creator(
|
|||
pool: &DbPool,
|
||||
comment: &Comment,
|
||||
) -> Result<ApubPerson, LemmyError> {
|
||||
let parent_creator_id = if let Some(parent_comment_id) = comment.parent_id {
|
||||
let parent_creator_id = if let Some(parent_comment_id) = comment.parent_comment_id() {
|
||||
let parent_comment =
|
||||
blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
|
||||
parent_comment.creator_id
|
||||
|
|
|
@ -98,7 +98,7 @@ impl ApubObject for ApubComment {
|
|||
})
|
||||
.await??;
|
||||
|
||||
let in_reply_to = if let Some(comment_id) = self.parent_id {
|
||||
let in_reply_to = if let Some(comment_id) = self.parent_comment_id() {
|
||||
let parent_comment =
|
||||
blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
|
||||
ObjectId::<PostOrComment>::new(parent_comment.ap_id)
|
||||
|
@ -170,7 +170,7 @@ impl ApubObject for ApubComment {
|
|||
.attributed_to
|
||||
.dereference(context, local_instance(context), request_counter)
|
||||
.await?;
|
||||
let (post, parent_comment_id) = note.get_parents(context, request_counter).await?;
|
||||
let (post, parent_comment) = note.get_parents(context, request_counter).await?;
|
||||
|
||||
let content = read_from_string_or_source(¬e.content, ¬e.media_type, ¬e.source);
|
||||
let content_slurs_removed = remove_slurs(&content, &context.settings().slur_regex());
|
||||
|
@ -178,17 +178,19 @@ impl ApubObject for ApubComment {
|
|||
let form = CommentForm {
|
||||
creator_id: creator.id,
|
||||
post_id: post.id,
|
||||
parent_id: parent_comment_id,
|
||||
content: content_slurs_removed,
|
||||
removed: None,
|
||||
read: None,
|
||||
published: note.published.map(|u| u.naive_local()),
|
||||
updated: note.updated.map(|u| u.naive_local()),
|
||||
deleted: None,
|
||||
ap_id: Some(note.id.into()),
|
||||
local: Some(false),
|
||||
};
|
||||
let comment = blocking(context.pool(), move |conn| Comment::upsert(conn, &form)).await??;
|
||||
let parent_comment_path = parent_comment.map(|t| t.0.path);
|
||||
let comment = blocking(context.pool(), move |conn| {
|
||||
Comment::create(conn, &form, parent_comment_path.as_ref())
|
||||
})
|
||||
.await??;
|
||||
Ok(comment.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use activitypub_federation::{
|
|||
use activitystreams_kinds::object::NoteType;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use lemmy_api_common::utils::blocking;
|
||||
use lemmy_db_schema::{newtypes::CommentId, source::post::Post, traits::Crud};
|
||||
use lemmy_db_schema::{source::post::Post, traits::Crud};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -51,7 +51,7 @@ impl Note {
|
|||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(ApubPost, Option<CommentId>), LemmyError> {
|
||||
) -> Result<(ApubPost, Option<ApubComment>), LemmyError> {
|
||||
// Fetch parent comment chain in a box, otherwise it can cause a stack overflow.
|
||||
let parent = Box::pin(
|
||||
self
|
||||
|
@ -61,16 +61,14 @@ impl Note {
|
|||
);
|
||||
match parent.deref() {
|
||||
PostOrComment::Post(p) => {
|
||||
// Workaround because I cant figure out how to get the post out of the box (and we dont
|
||||
// want to stackoverflow in a deep comment hierarchy).
|
||||
let post_id = p.id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
Ok((post.into(), None))
|
||||
let post = p.deref().to_owned();
|
||||
Ok((post, None))
|
||||
}
|
||||
PostOrComment::Comment(c) => {
|
||||
let post_id = c.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
Ok((post.into(), Some(c.id)))
|
||||
let comment = c.deref().to_owned();
|
||||
Ok((post.into(), Some(comment)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ diesel_migrations = { version = "1.4.0", optional = true }
|
|||
sha2 = { version = "0.10.2", optional = true }
|
||||
regex = { version = "1.5.5", optional = true }
|
||||
once_cell = { version = "1.10.0", optional = true }
|
||||
diesel_ltree = "0.2.7"
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.6.0"
|
||||
|
|
|
@ -74,17 +74,17 @@ mod tests {
|
|||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: Some(inserted_comment.id),
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
|
||||
let _inserted_child_comment =
|
||||
Comment::create(&conn, &child_comment_form, Some(&inserted_comment.path)).unwrap();
|
||||
|
||||
let comment_like = CommentLikeForm {
|
||||
comment_id: inserted_comment.id,
|
||||
|
|
|
@ -107,17 +107,17 @@ mod tests {
|
|||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: Some(inserted_comment.id),
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
|
||||
let _inserted_child_comment =
|
||||
Comment::create(&conn, &child_comment_form, Some(&inserted_comment.path)).unwrap();
|
||||
|
||||
let community_aggregates_before_delete =
|
||||
CommunityAggregates::read(&conn, inserted_community.id).unwrap();
|
||||
|
|
|
@ -78,7 +78,7 @@ mod tests {
|
|||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
let mut comment_like = CommentLikeForm {
|
||||
comment_id: inserted_comment.id,
|
||||
|
@ -89,15 +89,15 @@ mod tests {
|
|||
|
||||
let _inserted_comment_like = CommentLike::like(&conn, &comment_like).unwrap();
|
||||
|
||||
let mut child_comment_form = CommentForm {
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: Some(inserted_comment.id),
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
|
||||
let inserted_child_comment =
|
||||
Comment::create(&conn, &child_comment_form, Some(&inserted_comment.path)).unwrap();
|
||||
|
||||
let child_comment_like = CommentLikeForm {
|
||||
comment_id: inserted_child_comment.id,
|
||||
|
@ -123,14 +123,15 @@ mod tests {
|
|||
|
||||
// Remove a parent comment (the scores should also be removed)
|
||||
Comment::delete(&conn, inserted_comment.id).unwrap();
|
||||
Comment::delete(&conn, inserted_child_comment.id).unwrap();
|
||||
let after_parent_comment_delete = PersonAggregates::read(&conn, inserted_person.id).unwrap();
|
||||
assert_eq!(0, after_parent_comment_delete.comment_count);
|
||||
assert_eq!(0, after_parent_comment_delete.comment_score);
|
||||
|
||||
// Add in the two comments again, then delete the post.
|
||||
let new_parent_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
child_comment_form.parent_id = Some(new_parent_comment.id);
|
||||
Comment::create(&conn, &child_comment_form).unwrap();
|
||||
let new_parent_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
let _new_child_comment =
|
||||
Comment::create(&conn, &child_comment_form, Some(&new_parent_comment.path)).unwrap();
|
||||
comment_like.comment_id = new_parent_comment.id;
|
||||
CommentLike::like(&conn, &comment_like).unwrap();
|
||||
let after_comment_add = PersonAggregates::read(&conn, inserted_person.id).unwrap();
|
||||
|
|
|
@ -70,17 +70,17 @@ mod tests {
|
|||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: Some(inserted_comment.id),
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
|
||||
let inserted_child_comment =
|
||||
Comment::create(&conn, &child_comment_form, Some(&inserted_comment.path)).unwrap();
|
||||
|
||||
let post_like = PostLikeForm {
|
||||
post_id: inserted_post.id,
|
||||
|
@ -113,8 +113,9 @@ mod tests {
|
|||
assert_eq!(1, post_aggs_after_dislike.upvotes);
|
||||
assert_eq!(1, post_aggs_after_dislike.downvotes);
|
||||
|
||||
// Remove the parent comment
|
||||
// Remove the comments
|
||||
Comment::delete(&conn, inserted_comment.id).unwrap();
|
||||
Comment::delete(&conn, inserted_child_comment.id).unwrap();
|
||||
let after_comment_delete = PostAggregates::read(&conn, inserted_post.id).unwrap();
|
||||
assert_eq!(0, after_comment_delete.comments);
|
||||
assert_eq!(0, after_comment_delete.score);
|
||||
|
|
|
@ -72,17 +72,17 @@ mod tests {
|
|||
};
|
||||
|
||||
// Insert two of those comments
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
let child_comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: Some(inserted_comment.id),
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let _inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
|
||||
let _inserted_child_comment =
|
||||
Comment::create(&conn, &child_comment_form, Some(&inserted_comment.path)).unwrap();
|
||||
|
||||
let site_aggregates_before_delete = SiteAggregates::read(&conn).unwrap();
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ pub struct CommentAggregates {
|
|||
pub upvotes: i64,
|
||||
pub downvotes: i64,
|
||||
pub published: chrono::NaiveDateTime,
|
||||
pub child_count: i32,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::{
|
|||
utils::naive_now,
|
||||
};
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
use diesel_ltree::Ltree;
|
||||
use url::Url;
|
||||
|
||||
impl Comment {
|
||||
|
@ -74,17 +75,6 @@ impl Comment {
|
|||
.get_results::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_read(
|
||||
conn: &PgConnection,
|
||||
comment_id: CommentId,
|
||||
new_read: bool,
|
||||
) -> Result<Self, Error> {
|
||||
use crate::schema::comment::dsl::*;
|
||||
diesel::update(comment.find(comment_id))
|
||||
.set(read.eq(new_read))
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_content(
|
||||
conn: &PgConnection,
|
||||
comment_id: CommentId,
|
||||
|
@ -96,14 +86,71 @@ impl Comment {
|
|||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Comment, Error> {
|
||||
pub fn create(
|
||||
conn: &PgConnection,
|
||||
comment_form: &CommentForm,
|
||||
parent_path: Option<&Ltree>,
|
||||
) -> Result<Comment, Error> {
|
||||
use crate::schema::comment::dsl::*;
|
||||
insert_into(comment)
|
||||
|
||||
// Insert, to get the id
|
||||
let inserted_comment = insert_into(comment)
|
||||
.values(comment_form)
|
||||
.on_conflict(ap_id)
|
||||
.do_update()
|
||||
.set(comment_form)
|
||||
.get_result::<Self>(conn)
|
||||
.get_result::<Self>(conn);
|
||||
|
||||
if let Ok(comment_insert) = inserted_comment {
|
||||
let comment_id = comment_insert.id;
|
||||
|
||||
// You need to update the ltree column
|
||||
let ltree = Ltree(if let Some(parent_path) = parent_path {
|
||||
// The previous parent will already have 0 in it
|
||||
// Append this comment id
|
||||
format!("{}.{}", parent_path.0, comment_id)
|
||||
} else {
|
||||
// '0' is always the first path, append to that
|
||||
format!("{}.{}", 0, comment_id)
|
||||
});
|
||||
|
||||
let updated_comment = diesel::update(comment.find(comment_id))
|
||||
.set(path.eq(ltree))
|
||||
.get_result::<Self>(conn);
|
||||
|
||||
// Update the child count for the parent comment_aggregates
|
||||
// You could do this with a trigger, but since you have to do this manually anyway,
|
||||
// you can just have it here
|
||||
if let Some(parent_path) = parent_path {
|
||||
// You have to update counts for all parents, not just the immediate one
|
||||
// TODO if the performance of this is terrible, it might be better to do this as part of a
|
||||
// scheduled query... although the counts would often be wrong.
|
||||
//
|
||||
// The child_count query for reference:
|
||||
// select c.id, c.path, count(c2.id) as child_count from comment c
|
||||
// left join comment c2 on c2.path <@ c.path and c2.path != c.path
|
||||
// group by c.id
|
||||
|
||||
let top_parent = format!("0.{}", parent_path.0.split('.').collect::<Vec<&str>>()[1]);
|
||||
let update_child_count_stmt = format!(
|
||||
"
|
||||
update comment_aggregates ca set child_count = c.child_count
|
||||
from (
|
||||
select c.id, c.path, count(c2.id) as child_count from comment c
|
||||
join comment c2 on c2.path <@ c.path and c2.path != c.path
|
||||
and c.path <@ '{}'
|
||||
group by c.id
|
||||
) as c
|
||||
where ca.comment_id = c.id",
|
||||
top_parent
|
||||
);
|
||||
|
||||
sql_query(update_child_count_stmt).execute(conn)?;
|
||||
}
|
||||
updated_comment
|
||||
} else {
|
||||
inserted_comment
|
||||
}
|
||||
}
|
||||
pub fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error> {
|
||||
use crate::schema::comment::dsl::*;
|
||||
|
@ -116,6 +163,19 @@ impl Comment {
|
|||
.map(Into::into),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn parent_comment_id(&self) -> Option<CommentId> {
|
||||
let mut ltree_split: Vec<&str> = self.path.0.split('.').collect();
|
||||
ltree_split.remove(0); // The first is always 0
|
||||
if ltree_split.len() > 1 {
|
||||
ltree_split[ltree_split.len() - 2]
|
||||
.parse::<i32>()
|
||||
.map(CommentId)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Crud for Comment {
|
||||
|
@ -131,11 +191,9 @@ impl Crud for Comment {
|
|||
diesel::delete(comment.find(comment_id)).execute(conn)
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
|
||||
use crate::schema::comment::dsl::*;
|
||||
insert_into(comment)
|
||||
.values(comment_form)
|
||||
.get_result::<Self>(conn)
|
||||
/// This is unimplemented, use [[Comment::create]]
|
||||
fn create(_conn: &PgConnection, _comment_form: &CommentForm) -> Result<Self, Error> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn update(
|
||||
|
@ -218,6 +276,7 @@ mod tests {
|
|||
traits::{Crud, Likeable, Saveable},
|
||||
utils::establish_unpooled_connection,
|
||||
};
|
||||
use diesel_ltree::Ltree;
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
|
@ -258,7 +317,7 @@ mod tests {
|
|||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
let expected_comment = Comment {
|
||||
id: inserted_comment.id,
|
||||
|
@ -267,8 +326,7 @@ mod tests {
|
|||
post_id: inserted_post.id,
|
||||
removed: false,
|
||||
deleted: false,
|
||||
read: false,
|
||||
parent_id: None,
|
||||
path: Ltree(format!("0.{}", inserted_comment.id)),
|
||||
published: inserted_comment.published,
|
||||
updated: None,
|
||||
ap_id: inserted_comment.ap_id.to_owned(),
|
||||
|
@ -279,11 +337,12 @@ mod tests {
|
|||
content: "A child comment".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: Some(inserted_comment.id),
|
||||
// path: Some(text2ltree(inserted_comment.id),
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
|
||||
let inserted_child_comment =
|
||||
Comment::create(&conn, &child_comment_form, Some(&inserted_comment.path)).unwrap();
|
||||
|
||||
// Comment Like
|
||||
let comment_like_form = CommentLikeForm {
|
||||
|
@ -335,8 +394,8 @@ mod tests {
|
|||
assert_eq!(expected_comment_like, inserted_comment_like);
|
||||
assert_eq!(expected_comment_saved, inserted_comment_saved);
|
||||
assert_eq!(
|
||||
expected_comment.id,
|
||||
inserted_child_comment.parent_id.unwrap()
|
||||
format!("0.{}.{}", expected_comment.id, inserted_child_comment.id),
|
||||
inserted_child_comment.path.0,
|
||||
);
|
||||
assert_eq!(1, like_removed);
|
||||
assert_eq!(1, saved_removed);
|
||||
|
|
166
crates/db_schema/src/impls/comment_reply.rs
Normal file
166
crates/db_schema/src/impls/comment_reply.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
use crate::{
|
||||
newtypes::{CommentId, CommentReplyId, PersonId},
|
||||
source::comment_reply::*,
|
||||
traits::Crud,
|
||||
};
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
|
||||
impl Crud for CommentReply {
|
||||
type Form = CommentReplyForm;
|
||||
type IdType = CommentReplyId;
|
||||
fn read(conn: &PgConnection, comment_reply_id: CommentReplyId) -> Result<Self, Error> {
|
||||
use crate::schema::comment_reply::dsl::*;
|
||||
comment_reply.find(comment_reply_id).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, comment_reply_form: &CommentReplyForm) -> Result<Self, Error> {
|
||||
use crate::schema::comment_reply::dsl::*;
|
||||
// since the return here isnt utilized, we dont need to do an update
|
||||
// but get_result doesnt return the existing row here
|
||||
insert_into(comment_reply)
|
||||
.values(comment_reply_form)
|
||||
.on_conflict((recipient_id, comment_id))
|
||||
.do_update()
|
||||
.set(comment_reply_form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(
|
||||
conn: &PgConnection,
|
||||
comment_reply_id: CommentReplyId,
|
||||
comment_reply_form: &CommentReplyForm,
|
||||
) -> Result<Self, Error> {
|
||||
use crate::schema::comment_reply::dsl::*;
|
||||
diesel::update(comment_reply.find(comment_reply_id))
|
||||
.set(comment_reply_form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommentReply {
|
||||
pub fn update_read(
|
||||
conn: &PgConnection,
|
||||
comment_reply_id: CommentReplyId,
|
||||
new_read: bool,
|
||||
) -> Result<CommentReply, Error> {
|
||||
use crate::schema::comment_reply::dsl::*;
|
||||
diesel::update(comment_reply.find(comment_reply_id))
|
||||
.set(read.eq(new_read))
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn mark_all_as_read(
|
||||
conn: &PgConnection,
|
||||
for_recipient_id: PersonId,
|
||||
) -> Result<Vec<CommentReply>, Error> {
|
||||
use crate::schema::comment_reply::dsl::*;
|
||||
diesel::update(
|
||||
comment_reply
|
||||
.filter(recipient_id.eq(for_recipient_id))
|
||||
.filter(read.eq(false)),
|
||||
)
|
||||
.set(read.eq(true))
|
||||
.get_results::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn read_by_comment(conn: &PgConnection, for_comment_id: CommentId) -> Result<Self, Error> {
|
||||
use crate::schema::comment_reply::dsl::*;
|
||||
comment_reply
|
||||
.filter(comment_id.eq(for_comment_id))
|
||||
.first::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
source::{
|
||||
comment::*,
|
||||
comment_reply::*,
|
||||
community::{Community, CommunityForm},
|
||||
person::*,
|
||||
post::*,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::establish_unpooled_connection,
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let new_person = PersonForm {
|
||||
name: "terrylake".into(),
|
||||
public_key: Some("pubkey".to_string()),
|
||||
..PersonForm::default()
|
||||
};
|
||||
|
||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||
|
||||
let recipient_form = PersonForm {
|
||||
name: "terrylakes recipient".into(),
|
||||
public_key: Some("pubkey".to_string()),
|
||||
..PersonForm::default()
|
||||
};
|
||||
|
||||
let inserted_recipient = Person::create(&conn, &recipient_form).unwrap();
|
||||
|
||||
let new_community = CommunityForm {
|
||||
name: "test community lake".to_string(),
|
||||
title: "nada".to_owned(),
|
||||
public_key: Some("pubkey".to_string()),
|
||||
..CommunityForm::default()
|
||||
};
|
||||
|
||||
let inserted_community = Community::create(&conn, &new_community).unwrap();
|
||||
|
||||
let new_post = PostForm {
|
||||
name: "A test post".into(),
|
||||
creator_id: inserted_person.id,
|
||||
community_id: inserted_community.id,
|
||||
..PostForm::default()
|
||||
};
|
||||
|
||||
let inserted_post = Post::create(&conn, &new_post).unwrap();
|
||||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
let comment_reply_form = CommentReplyForm {
|
||||
recipient_id: inserted_recipient.id,
|
||||
comment_id: inserted_comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
let inserted_reply = CommentReply::create(&conn, &comment_reply_form).unwrap();
|
||||
|
||||
let expected_reply = CommentReply {
|
||||
id: inserted_reply.id,
|
||||
recipient_id: inserted_reply.recipient_id,
|
||||
comment_id: inserted_reply.comment_id,
|
||||
read: false,
|
||||
published: inserted_reply.published,
|
||||
};
|
||||
|
||||
let read_reply = CommentReply::read(&conn, inserted_reply.id).unwrap();
|
||||
let updated_reply =
|
||||
CommentReply::update(&conn, inserted_reply.id, &comment_reply_form).unwrap();
|
||||
Comment::delete(&conn, inserted_comment.id).unwrap();
|
||||
Post::delete(&conn, inserted_post.id).unwrap();
|
||||
Community::delete(&conn, inserted_community.id).unwrap();
|
||||
Person::delete(&conn, inserted_person.id).unwrap();
|
||||
Person::delete(&conn, inserted_recipient.id).unwrap();
|
||||
|
||||
assert_eq!(expected_reply, read_reply);
|
||||
assert_eq!(expected_reply, inserted_reply);
|
||||
assert_eq!(expected_reply, updated_reply);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
pub mod activity;
|
||||
pub mod comment;
|
||||
pub mod comment_reply;
|
||||
pub mod comment_report;
|
||||
pub mod community;
|
||||
pub mod community_block;
|
||||
|
|
|
@ -411,7 +411,7 @@ mod tests {
|
|||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
// Now the actual tests
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ mod tests {
|
|||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
let person_mention_form = PersonMentionForm {
|
||||
recipient_id: inserted_recipient.id,
|
||||
|
|
|
@ -30,6 +30,7 @@ pub enum SortType {
|
|||
Active,
|
||||
Hot,
|
||||
New,
|
||||
Old,
|
||||
TopDay,
|
||||
TopWeek,
|
||||
TopMonth,
|
||||
|
@ -39,6 +40,14 @@ pub enum SortType {
|
|||
NewComments,
|
||||
}
|
||||
|
||||
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
pub enum CommentSortType {
|
||||
Hot,
|
||||
Top,
|
||||
New,
|
||||
Old,
|
||||
}
|
||||
|
||||
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
|
||||
pub enum ListingType {
|
||||
All,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use diesel_ltree::Ltree;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt,
|
||||
|
@ -68,12 +69,21 @@ pub struct CommentReportId(i32);
|
|||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||
pub struct PostReportId(i32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||
pub struct CommentReplyId(i32);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
|
||||
#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))]
|
||||
#[cfg_attr(feature = "full", sql_type = "diesel::sql_types::Text")]
|
||||
pub struct DbUrl(pub(crate) Url);
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "Ltree")]
|
||||
/// Do remote derivation for the Ltree struct
|
||||
pub struct LtreeDef(pub String);
|
||||
|
||||
impl Display for DbUrl {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.to_owned().0.fmt(f)
|
||||
|
|
|
@ -11,19 +11,21 @@ table! {
|
|||
}
|
||||
|
||||
table! {
|
||||
use diesel_ltree::sql_types::Ltree;
|
||||
use diesel::sql_types::*;
|
||||
|
||||
comment (id) {
|
||||
id -> Int4,
|
||||
creator_id -> Int4,
|
||||
post_id -> Int4,
|
||||
parent_id -> Nullable<Int4>,
|
||||
content -> Text,
|
||||
removed -> Bool,
|
||||
read -> Bool,
|
||||
published -> Timestamp,
|
||||
updated -> Nullable<Timestamp>,
|
||||
deleted -> Bool,
|
||||
ap_id -> Varchar,
|
||||
local -> Bool,
|
||||
path -> Ltree,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,6 +37,7 @@ table! {
|
|||
upvotes -> Int8,
|
||||
downvotes -> Int8,
|
||||
published -> Timestamp,
|
||||
child_count -> Int4,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,6 +343,16 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
comment_reply (id) {
|
||||
id -> Int4,
|
||||
recipient_id -> Int4,
|
||||
comment_id -> Int4,
|
||||
read -> Bool,
|
||||
published -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
post (id) {
|
||||
id -> Int4,
|
||||
|
@ -501,23 +514,6 @@ table! {
|
|||
}
|
||||
|
||||
// These are necessary since diesel doesn't have self joins / aliases
|
||||
table! {
|
||||
comment_alias_1 (id) {
|
||||
id -> Int4,
|
||||
creator_id -> Int4,
|
||||
post_id -> Int4,
|
||||
parent_id -> Nullable<Int4>,
|
||||
content -> Text,
|
||||
removed -> Bool,
|
||||
read -> Bool,
|
||||
published -> Timestamp,
|
||||
updated -> Nullable<Timestamp>,
|
||||
deleted -> Bool,
|
||||
ap_id -> Varchar,
|
||||
local -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
person_alias_1 (id) {
|
||||
id -> Int4,
|
||||
|
@ -647,9 +643,8 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
joinable!(comment_alias_1 -> person_alias_1 (creator_id));
|
||||
joinable!(comment -> comment_alias_1 (parent_id));
|
||||
joinable!(person_mention -> person_alias_1 (recipient_id));
|
||||
joinable!(comment_reply -> person_alias_1 (recipient_id));
|
||||
joinable!(post -> person_alias_1 (creator_id));
|
||||
joinable!(comment -> person_alias_1 (creator_id));
|
||||
|
||||
|
@ -696,6 +691,8 @@ joinable!(person_aggregates -> person (person_id));
|
|||
joinable!(person_ban -> person (person_id));
|
||||
joinable!(person_mention -> comment (comment_id));
|
||||
joinable!(person_mention -> person (recipient_id));
|
||||
joinable!(comment_reply -> comment (comment_id));
|
||||
joinable!(comment_reply -> person (recipient_id));
|
||||
joinable!(post -> community (community_id));
|
||||
joinable!(post -> person (creator_id));
|
||||
joinable!(post_aggregates -> post (post_id));
|
||||
|
@ -751,6 +748,7 @@ allow_tables_to_appear_in_same_query!(
|
|||
person_ban,
|
||||
person_block,
|
||||
person_mention,
|
||||
comment_reply,
|
||||
post,
|
||||
post_aggregates,
|
||||
post_like,
|
||||
|
@ -760,7 +758,6 @@ allow_tables_to_appear_in_same_query!(
|
|||
private_message,
|
||||
site,
|
||||
site_aggregates,
|
||||
comment_alias_1,
|
||||
person_alias_1,
|
||||
person_alias_2,
|
||||
admin_purge_comment,
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
use crate::newtypes::{CommentId, DbUrl, PersonId, PostId};
|
||||
use crate::newtypes::{CommentId, DbUrl, LtreeDef, PersonId, PostId};
|
||||
use diesel_ltree::Ltree;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::{comment, comment_alias_1, comment_like, comment_saved};
|
||||
|
||||
// WITH RECURSIVE MyTree AS (
|
||||
// SELECT * FROM comment WHERE parent_id IS NULL
|
||||
// UNION ALL
|
||||
// SELECT m.* FROM comment AS m JOIN MyTree AS t ON m.parent_id = t.id
|
||||
// )
|
||||
// SELECT * FROM MyTree;
|
||||
use crate::schema::{comment, comment_like, comment_saved};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
|
||||
|
@ -19,34 +13,15 @@ pub struct Comment {
|
|||
pub id: CommentId,
|
||||
pub creator_id: PersonId,
|
||||
pub post_id: PostId,
|
||||
pub parent_id: Option<CommentId>,
|
||||
pub content: String,
|
||||
pub removed: bool,
|
||||
pub read: bool, // Whether the recipient has read the comment or not
|
||||
pub published: chrono::NaiveDateTime,
|
||||
pub updated: Option<chrono::NaiveDateTime>,
|
||||
pub deleted: bool,
|
||||
pub ap_id: DbUrl,
|
||||
pub local: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
|
||||
#[cfg_attr(feature = "full", belongs_to(crate::source::post::Post))]
|
||||
#[cfg_attr(feature = "full", table_name = "comment_alias_1")]
|
||||
pub struct CommentAlias1 {
|
||||
pub id: CommentId,
|
||||
pub creator_id: PersonId,
|
||||
pub post_id: PostId,
|
||||
pub parent_id: Option<CommentId>,
|
||||
pub content: String,
|
||||
pub removed: bool,
|
||||
pub read: bool, // Whether the recipient has read the comment or not
|
||||
pub published: chrono::NaiveDateTime,
|
||||
pub updated: Option<chrono::NaiveDateTime>,
|
||||
pub deleted: bool,
|
||||
pub ap_id: DbUrl,
|
||||
pub local: bool,
|
||||
#[serde(with = "LtreeDef")]
|
||||
pub path: Ltree,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
@ -56,9 +31,7 @@ pub struct CommentForm {
|
|||
pub creator_id: PersonId,
|
||||
pub post_id: PostId,
|
||||
pub content: String,
|
||||
pub parent_id: Option<CommentId>,
|
||||
pub removed: Option<bool>,
|
||||
pub read: Option<bool>,
|
||||
pub published: Option<chrono::NaiveDateTime>,
|
||||
pub updated: Option<chrono::NaiveDateTime>,
|
||||
pub deleted: Option<bool>,
|
||||
|
|
26
crates/db_schema/src/source/comment_reply.rs
Normal file
26
crates/db_schema/src/source/comment_reply.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use crate::newtypes::{CommentId, CommentReplyId, PersonId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::comment_reply;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
|
||||
#[cfg_attr(feature = "full", belongs_to(crate::source::comment::Comment))]
|
||||
#[cfg_attr(feature = "full", table_name = "comment_reply")]
|
||||
/// This table keeps a list of replies to comments and posts.
|
||||
pub struct CommentReply {
|
||||
pub id: CommentReplyId,
|
||||
pub recipient_id: PersonId,
|
||||
pub comment_id: CommentId,
|
||||
pub read: bool,
|
||||
pub published: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", table_name = "comment_reply")]
|
||||
pub struct CommentReplyForm {
|
||||
pub recipient_id: PersonId,
|
||||
pub comment_id: CommentId,
|
||||
pub read: Option<bool>,
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#[cfg(feature = "full")]
|
||||
pub mod activity;
|
||||
pub mod comment;
|
||||
pub mod comment_reply;
|
||||
pub mod comment_report;
|
||||
pub mod community;
|
||||
pub mod community_block;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::newtypes::DbUrl;
|
||||
use crate::{newtypes::DbUrl, CommentSortType, SortType};
|
||||
use activitypub_federation::{core::object_id::ObjectId, traits::ApubObject};
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::{
|
||||
|
@ -118,6 +118,19 @@ pub fn naive_now() -> NaiveDateTime {
|
|||
chrono::prelude::Utc::now().naive_utc()
|
||||
}
|
||||
|
||||
pub fn post_to_comment_sort_type(sort: SortType) -> CommentSortType {
|
||||
match sort {
|
||||
SortType::Active | SortType::Hot => CommentSortType::Hot,
|
||||
SortType::New | SortType::NewComments | SortType::MostComments => CommentSortType::New,
|
||||
SortType::Old => CommentSortType::Old,
|
||||
SortType::TopDay
|
||||
| SortType::TopAll
|
||||
| SortType::TopWeek
|
||||
| SortType::TopYear
|
||||
| SortType::TopMonth => CommentSortType::Top,
|
||||
}
|
||||
}
|
||||
|
||||
static EMAIL_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
|
||||
.expect("compile email regex")
|
||||
|
|
|
@ -19,6 +19,7 @@ lemmy_db_schema = { version = "=0.16.5", path = "../db_schema" }
|
|||
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"], optional = true }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
tracing = { version = "0.1.32", optional = true }
|
||||
diesel_ltree = "0.2.7"
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.6.0"
|
||||
|
|
|
@ -377,7 +377,7 @@ mod tests {
|
|||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||
|
||||
// sara reports
|
||||
let sara_report_form = CommentReportForm {
|
||||
|
@ -472,6 +472,7 @@ mod tests {
|
|||
upvotes: 0,
|
||||
downvotes: 0,
|
||||
published: agg.published,
|
||||
child_count: 0,
|
||||
},
|
||||
my_vote: None,
|
||||
resolver: None,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use crate::structs::CommentView;
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions};
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::CommentAggregates,
|
||||
newtypes::{CommentId, CommunityId, DbUrl, PersonId, PostId},
|
||||
schema::{
|
||||
comment,
|
||||
comment_aggregates,
|
||||
comment_alias_1,
|
||||
comment_like,
|
||||
comment_saved,
|
||||
community,
|
||||
|
@ -14,28 +14,25 @@ use lemmy_db_schema::{
|
|||
community_follower,
|
||||
community_person_ban,
|
||||
person,
|
||||
person_alias_1,
|
||||
person_block,
|
||||
post,
|
||||
},
|
||||
source::{
|
||||
comment::{Comment, CommentAlias1, CommentSaved},
|
||||
comment::{Comment, CommentSaved},
|
||||
community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
|
||||
person::{Person, PersonAlias1, PersonSafe, PersonSafeAlias1},
|
||||
person::{Person, PersonSafe},
|
||||
person_block::PersonBlock,
|
||||
post::Post,
|
||||
},
|
||||
traits::{MaybeOptional, ToSafe, ViewToVec},
|
||||
utils::{functions::hot_rank, fuzzy_search, limit_and_offset_unlimited},
|
||||
CommentSortType,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
|
||||
type CommentViewTuple = (
|
||||
Comment,
|
||||
PersonSafe,
|
||||
Option<CommentAlias1>,
|
||||
Option<PersonSafeAlias1>,
|
||||
Post,
|
||||
CommunitySafe,
|
||||
CommentAggregates,
|
||||
|
@ -58,8 +55,6 @@ impl CommentView {
|
|||
let (
|
||||
comment,
|
||||
creator,
|
||||
_parent_comment,
|
||||
recipient,
|
||||
post,
|
||||
community,
|
||||
counts,
|
||||
|
@ -71,9 +66,6 @@ impl CommentView {
|
|||
) = comment::table
|
||||
.find(comment_id)
|
||||
.inner_join(person::table)
|
||||
// recipient here
|
||||
.left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
|
||||
.left_join(person_alias_1::table.on(person_alias_1::id.eq(comment_alias_1::creator_id)))
|
||||
.inner_join(post::table)
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(comment_aggregates::table)
|
||||
|
@ -120,8 +112,6 @@ impl CommentView {
|
|||
.select((
|
||||
comment::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
comment_alias_1::all_columns.nullable(),
|
||||
PersonAlias1::safe_columns_tuple().nullable(),
|
||||
post::all_columns,
|
||||
Community::safe_columns_tuple(),
|
||||
comment_aggregates::all_columns,
|
||||
|
@ -143,7 +133,6 @@ impl CommentView {
|
|||
|
||||
Ok(CommentView {
|
||||
comment,
|
||||
recipient,
|
||||
post,
|
||||
creator,
|
||||
community,
|
||||
|
@ -155,73 +144,24 @@ impl CommentView {
|
|||
my_vote,
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the recipient person id.
|
||||
/// If there is no parent comment, its the post creator
|
||||
pub fn get_recipient_id(&self) -> PersonId {
|
||||
match &self.recipient {
|
||||
Some(parent_commenter) => parent_commenter.id,
|
||||
None => self.post.creator_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the number of unread replies
|
||||
pub fn get_unread_replies(conn: &PgConnection, my_person_id: PersonId) -> Result<i64, Error> {
|
||||
use diesel::dsl::*;
|
||||
|
||||
comment::table
|
||||
// recipient here
|
||||
.left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
|
||||
.left_join(person_alias_1::table.on(person_alias_1::id.eq(comment_alias_1::creator_id)))
|
||||
.inner_join(post::table)
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.left_join(
|
||||
person_block::table.on(
|
||||
comment::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
community_block::table.on(
|
||||
community::id
|
||||
.eq(community_block::community_id)
|
||||
.and(community_block::person_id.eq(my_person_id)),
|
||||
),
|
||||
)
|
||||
.filter(person_alias_1::id.eq(my_person_id)) // Gets the comment replies
|
||||
.or_filter(
|
||||
comment::parent_id
|
||||
.is_null()
|
||||
.and(post::creator_id.eq(my_person_id)),
|
||||
) // Gets the top level replies
|
||||
.filter(comment::read.eq(false))
|
||||
.filter(comment::deleted.eq(false))
|
||||
.filter(comment::removed.eq(false))
|
||||
// Don't show blocked communities or persons
|
||||
.filter(community_block::person_id.is_null())
|
||||
.filter(person_block::person_id.is_null())
|
||||
.select(count(comment::id))
|
||||
.first::<i64>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommentQueryBuilder<'a> {
|
||||
conn: &'a PgConnection,
|
||||
listing_type: Option<ListingType>,
|
||||
sort: Option<SortType>,
|
||||
sort: Option<CommentSortType>,
|
||||
community_id: Option<CommunityId>,
|
||||
community_actor_id: Option<DbUrl>,
|
||||
post_id: Option<PostId>,
|
||||
parent_path: Option<Ltree>,
|
||||
creator_id: Option<PersonId>,
|
||||
recipient_id: Option<PersonId>,
|
||||
my_person_id: Option<PersonId>,
|
||||
search_term: Option<String>,
|
||||
saved_only: Option<bool>,
|
||||
unread_only: Option<bool>,
|
||||
show_bot_accounts: Option<bool>,
|
||||
page: Option<i64>,
|
||||
limit: Option<i64>,
|
||||
max_depth: Option<i32>,
|
||||
}
|
||||
|
||||
impl<'a> CommentQueryBuilder<'a> {
|
||||
|
@ -233,15 +173,15 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
community_id: None,
|
||||
community_actor_id: None,
|
||||
post_id: None,
|
||||
parent_path: None,
|
||||
creator_id: None,
|
||||
recipient_id: None,
|
||||
my_person_id: None,
|
||||
search_term: None,
|
||||
saved_only: None,
|
||||
unread_only: None,
|
||||
show_bot_accounts: None,
|
||||
page: None,
|
||||
limit: None,
|
||||
max_depth: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,7 +190,7 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn sort<T: MaybeOptional<SortType>>(mut self, sort: T) -> Self {
|
||||
pub fn sort<T: MaybeOptional<CommentSortType>>(mut self, sort: T) -> Self {
|
||||
self.sort = sort.get_optional();
|
||||
self
|
||||
}
|
||||
|
@ -265,11 +205,6 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn recipient_id<T: MaybeOptional<PersonId>>(mut self, recipient_id: T) -> Self {
|
||||
self.recipient_id = recipient_id.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn community_id<T: MaybeOptional<CommunityId>>(mut self, community_id: T) -> Self {
|
||||
self.community_id = community_id.get_optional();
|
||||
self
|
||||
|
@ -295,13 +230,13 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn unread_only<T: MaybeOptional<bool>>(mut self, unread_only: T) -> Self {
|
||||
self.unread_only = unread_only.get_optional();
|
||||
pub fn show_bot_accounts<T: MaybeOptional<bool>>(mut self, show_bot_accounts: T) -> Self {
|
||||
self.show_bot_accounts = show_bot_accounts.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show_bot_accounts<T: MaybeOptional<bool>>(mut self, show_bot_accounts: T) -> Self {
|
||||
self.show_bot_accounts = show_bot_accounts.get_optional();
|
||||
pub fn parent_path<T: MaybeOptional<Ltree>>(mut self, parent_path: T) -> Self {
|
||||
self.parent_path = parent_path.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -315,6 +250,11 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn max_depth<T: MaybeOptional<i32>>(mut self, max_depth: T) -> Self {
|
||||
self.max_depth = max_depth.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<CommentView>, Error> {
|
||||
use diesel::dsl::*;
|
||||
|
||||
|
@ -323,9 +263,6 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
|
||||
let mut query = comment::table
|
||||
.inner_join(person::table)
|
||||
// recipient here
|
||||
.left_join(comment_alias_1::table.on(comment_alias_1::id.nullable().eq(comment::parent_id)))
|
||||
.left_join(person_alias_1::table.on(person_alias_1::id.eq(comment_alias_1::creator_id)))
|
||||
.inner_join(post::table)
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(comment_aggregates::table)
|
||||
|
@ -379,8 +316,6 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
.select((
|
||||
comment::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
comment_alias_1::all_columns.nullable(),
|
||||
PersonAlias1::safe_columns_tuple().nullable(),
|
||||
post::all_columns,
|
||||
Community::safe_columns_tuple(),
|
||||
comment_aggregates::all_columns,
|
||||
|
@ -392,24 +327,6 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
))
|
||||
.into_boxed();
|
||||
|
||||
// The replies
|
||||
if let Some(recipient_id) = self.recipient_id {
|
||||
query = query
|
||||
// TODO needs lots of testing
|
||||
.filter(person_alias_1::id.eq(recipient_id)) // Gets the comment replies
|
||||
.or_filter(
|
||||
comment::parent_id
|
||||
.is_null()
|
||||
.and(post::creator_id.eq(recipient_id)),
|
||||
) // Gets the top level replies
|
||||
.filter(comment::deleted.eq(false))
|
||||
.filter(comment::removed.eq(false));
|
||||
}
|
||||
|
||||
if self.unread_only.unwrap_or(false) {
|
||||
query = query.filter(comment::read.eq(false));
|
||||
}
|
||||
|
||||
if let Some(creator_id) = self.creator_id {
|
||||
query = query.filter(comment::creator_id.eq(creator_id));
|
||||
};
|
||||
|
@ -418,6 +335,10 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
query = query.filter(comment::post_id.eq(post_id));
|
||||
};
|
||||
|
||||
if let Some(parent_path) = self.parent_path.as_ref() {
|
||||
query = query.filter(comment::path.contained_by(parent_path));
|
||||
};
|
||||
|
||||
if let Some(search_term) = self.search_term {
|
||||
query = query.filter(comment::content.ilike(fuzzy_search(&search_term)));
|
||||
};
|
||||
|
@ -460,36 +381,47 @@ impl<'a> CommentQueryBuilder<'a> {
|
|||
query = query.filter(person::bot_account.eq(false));
|
||||
};
|
||||
|
||||
query = match self.sort.unwrap_or(SortType::New) {
|
||||
SortType::Hot | SortType::Active => query
|
||||
.order_by(hot_rank(comment_aggregates::score, comment_aggregates::published).desc())
|
||||
.then_order_by(comment_aggregates::published.desc()),
|
||||
SortType::New | SortType::MostComments | SortType::NewComments => {
|
||||
query.order_by(comment::published.desc())
|
||||
}
|
||||
SortType::TopAll => query.order_by(comment_aggregates::score.desc()),
|
||||
SortType::TopYear => query
|
||||
.filter(comment::published.gt(now - 1.years()))
|
||||
.order_by(comment_aggregates::score.desc()),
|
||||
SortType::TopMonth => query
|
||||
.filter(comment::published.gt(now - 1.months()))
|
||||
.order_by(comment_aggregates::score.desc()),
|
||||
SortType::TopWeek => query
|
||||
.filter(comment::published.gt(now - 1.weeks()))
|
||||
.order_by(comment_aggregates::score.desc()),
|
||||
SortType::TopDay => query
|
||||
.filter(comment::published.gt(now - 1.days()))
|
||||
.order_by(comment_aggregates::score.desc()),
|
||||
};
|
||||
|
||||
// Don't show blocked communities or persons
|
||||
if self.my_person_id.is_some() {
|
||||
query = query.filter(community_block::person_id.is_null());
|
||||
query = query.filter(person_block::person_id.is_null());
|
||||
}
|
||||
|
||||
// Don't use the regular error-checking one, many more comments must ofter be fetched.
|
||||
let (limit, offset) = limit_and_offset_unlimited(self.page, self.limit);
|
||||
// A Max depth given means its a tree fetch
|
||||
let (limit, offset) = if let Some(max_depth) = self.max_depth {
|
||||
let depth_limit = if let Some(parent_path) = self.parent_path.as_ref() {
|
||||
parent_path.0.split('.').count() as i32 + max_depth
|
||||
// Add one because of root "0"
|
||||
} else {
|
||||
max_depth + 1
|
||||
};
|
||||
|
||||
query = query.filter(nlevel(comment::path).le(depth_limit));
|
||||
|
||||
// Always order by the parent path first
|
||||
query = query.order_by(subpath(comment::path, 0, -1));
|
||||
|
||||
// TODO limit question. Limiting does not work for comment threads ATM, only max_depth
|
||||
// For now, don't do any limiting for tree fetches
|
||||
// https://stackoverflow.com/questions/72983614/postgres-ltree-how-to-limit-the-max-number-of-children-at-any-given-level
|
||||
|
||||
// Don't use the regular error-checking one, many more comments must ofter be fetched.
|
||||
// This does not work for comment trees, and the limit should be manually set to a high number
|
||||
//
|
||||
// If a max depth is given, then you know its a tree fetch, and limits should be ignored
|
||||
(i64::MAX, 0)
|
||||
} else {
|
||||
limit_and_offset_unlimited(self.page, self.limit)
|
||||
};
|
||||
|
||||
query = match self.sort.unwrap_or(CommentSortType::Hot) {
|
||||
CommentSortType::Hot => query
|
||||
.then_order_by(hot_rank(comment_aggregates::score, comment_aggregates::published).desc())
|
||||
.then_order_by(comment_aggregates::published.desc()),
|
||||
CommentSortType::New => query.then_order_by(comment::published.desc()),
|
||||
CommentSortType::Old => query.then_order_by(comment::published.asc()),
|
||||
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
|
||||
};
|
||||
|
||||
// Note: deleted and removed comments are done on the front side
|
||||
let res = query
|
||||
|
@ -509,15 +441,14 @@ impl ViewToVec for CommentView {
|
|||
.map(|a| Self {
|
||||
comment: a.0.to_owned(),
|
||||
creator: a.1.to_owned(),
|
||||
recipient: a.3.to_owned(),
|
||||
post: a.4.to_owned(),
|
||||
community: a.5.to_owned(),
|
||||
counts: a.6.to_owned(),
|
||||
creator_banned_from_community: a.7.is_some(),
|
||||
subscribed: CommunityFollower::to_subscribed_type(&a.8),
|
||||
saved: a.9.is_some(),
|
||||
creator_blocked: a.10.is_some(),
|
||||
my_vote: a.11,
|
||||
post: a.2.to_owned(),
|
||||
community: a.3.to_owned(),
|
||||
counts: a.4.to_owned(),
|
||||
creator_banned_from_community: a.5.is_some(),
|
||||
subscribed: CommunityFollower::to_subscribed_type(&a.6),
|
||||
saved: a.7.is_some(),
|
||||
creator_blocked: a.8.is_some(),
|
||||
my_vote: a.9,
|
||||
})
|
||||
.collect::<Vec<Self>>()
|
||||
}
|
||||
|
@ -574,24 +505,72 @@ mod tests {
|
|||
|
||||
let inserted_post = Post::create(&conn, &new_post).unwrap();
|
||||
|
||||
let comment_form = CommentForm {
|
||||
content: "A test comment 32".into(),
|
||||
// Create a comment tree with this hierarchy
|
||||
// 0
|
||||
// \ \
|
||||
// 1 2
|
||||
// \
|
||||
// 3 4
|
||||
// \
|
||||
// 5
|
||||
let comment_form_0 = CommentForm {
|
||||
content: "Comment 0".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
||||
let inserted_comment_0 = Comment::create(&conn, &comment_form_0, None).unwrap();
|
||||
|
||||
let comment_form_2 = CommentForm {
|
||||
content: "A test blocked comment".into(),
|
||||
let comment_form_1 = CommentForm {
|
||||
content: "Comment 1, A test blocked comment".into(),
|
||||
creator_id: inserted_person_2.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: Some(inserted_comment.id),
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment_2 = Comment::create(&conn, &comment_form_2).unwrap();
|
||||
let inserted_comment_1 =
|
||||
Comment::create(&conn, &comment_form_1, Some(&inserted_comment_0.path)).unwrap();
|
||||
|
||||
let comment_form_2 = CommentForm {
|
||||
content: "Comment 2".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment_2 =
|
||||
Comment::create(&conn, &comment_form_2, Some(&inserted_comment_0.path)).unwrap();
|
||||
|
||||
let comment_form_3 = CommentForm {
|
||||
content: "Comment 3".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let _inserted_comment_3 =
|
||||
Comment::create(&conn, &comment_form_3, Some(&inserted_comment_1.path)).unwrap();
|
||||
|
||||
let comment_form_4 = CommentForm {
|
||||
content: "Comment 4".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let inserted_comment_4 =
|
||||
Comment::create(&conn, &comment_form_4, Some(&inserted_comment_1.path)).unwrap();
|
||||
|
||||
let comment_form_5 = CommentForm {
|
||||
content: "Comment 5".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
..CommentForm::default()
|
||||
};
|
||||
|
||||
let _inserted_comment_5 =
|
||||
Comment::create(&conn, &comment_form_5, Some(&inserted_comment_4.path)).unwrap();
|
||||
|
||||
let timmy_blocks_sara_form = PersonBlockForm {
|
||||
person_id: inserted_person.id,
|
||||
|
@ -610,7 +589,7 @@ mod tests {
|
|||
assert_eq!(expected_block, inserted_block);
|
||||
|
||||
let comment_like_form = CommentLikeForm {
|
||||
comment_id: inserted_comment.id,
|
||||
comment_id: inserted_comment_0.id,
|
||||
post_id: inserted_post.id,
|
||||
person_id: inserted_person.id,
|
||||
score: 1,
|
||||
|
@ -618,8 +597,9 @@ mod tests {
|
|||
|
||||
let _inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
|
||||
|
||||
let agg = CommentAggregates::read(&conn, inserted_comment.id).unwrap();
|
||||
let agg = CommentAggregates::read(&conn, inserted_comment_0.id).unwrap();
|
||||
|
||||
let top_path = inserted_comment_0.to_owned().path;
|
||||
let expected_comment_view_no_person = CommentView {
|
||||
creator_banned_from_community: false,
|
||||
my_vote: None,
|
||||
|
@ -627,18 +607,17 @@ mod tests {
|
|||
saved: false,
|
||||
creator_blocked: false,
|
||||
comment: Comment {
|
||||
id: inserted_comment.id,
|
||||
content: "A test comment 32".into(),
|
||||
id: inserted_comment_0.id,
|
||||
content: "Comment 0".into(),
|
||||
creator_id: inserted_person.id,
|
||||
post_id: inserted_post.id,
|
||||
parent_id: None,
|
||||
removed: false,
|
||||
deleted: false,
|
||||
read: false,
|
||||
published: inserted_comment.published,
|
||||
ap_id: inserted_comment.ap_id,
|
||||
published: inserted_comment_0.published,
|
||||
ap_id: inserted_comment_0.ap_id,
|
||||
updated: None,
|
||||
local: true,
|
||||
path: top_path,
|
||||
},
|
||||
creator: PersonSafe {
|
||||
id: inserted_person.id,
|
||||
|
@ -660,7 +639,6 @@ mod tests {
|
|||
matrix_user_id: None,
|
||||
ban_expires: None,
|
||||
},
|
||||
recipient: None,
|
||||
post: Post {
|
||||
id: inserted_post.id,
|
||||
name: inserted_post.name.to_owned(),
|
||||
|
@ -701,11 +679,12 @@ mod tests {
|
|||
},
|
||||
counts: CommentAggregates {
|
||||
id: agg.id,
|
||||
comment_id: inserted_comment.id,
|
||||
comment_id: inserted_comment_0.id,
|
||||
score: 1,
|
||||
upvotes: 1,
|
||||
downvotes: 0,
|
||||
published: agg.published,
|
||||
child_count: 5,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -717,38 +696,96 @@ mod tests {
|
|||
.list()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_comment_view_no_person,
|
||||
read_comment_views_no_person[0]
|
||||
);
|
||||
|
||||
let read_comment_views_with_person = CommentQueryBuilder::create(&conn)
|
||||
.post_id(inserted_post.id)
|
||||
.my_person_id(inserted_person.id)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
let read_comment_from_blocked_person =
|
||||
CommentView::read(&conn, inserted_comment_2.id, Some(inserted_person.id)).unwrap();
|
||||
|
||||
let like_removed = CommentLike::remove(&conn, inserted_person.id, inserted_comment.id).unwrap();
|
||||
let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
|
||||
Comment::delete(&conn, inserted_comment_2.id).unwrap();
|
||||
Post::delete(&conn, inserted_post.id).unwrap();
|
||||
Community::delete(&conn, inserted_community.id).unwrap();
|
||||
Person::delete(&conn, inserted_person.id).unwrap();
|
||||
Person::delete(&conn, inserted_person_2.id).unwrap();
|
||||
|
||||
// Make sure its 1, not showing the blocked comment
|
||||
assert_eq!(1, read_comment_views_with_person.len());
|
||||
|
||||
assert_eq!(
|
||||
expected_comment_view_no_person,
|
||||
read_comment_views_no_person[1]
|
||||
);
|
||||
assert_eq!(
|
||||
expected_comment_view_with_person,
|
||||
read_comment_views_with_person[0]
|
||||
);
|
||||
|
||||
// Make sure its 1, not showing the blocked comment
|
||||
assert_eq!(5, read_comment_views_with_person.len());
|
||||
|
||||
let read_comment_from_blocked_person =
|
||||
CommentView::read(&conn, inserted_comment_1.id, Some(inserted_person.id)).unwrap();
|
||||
|
||||
// Make sure block set the creator blocked
|
||||
assert!(read_comment_from_blocked_person.creator_blocked);
|
||||
|
||||
let top_path = inserted_comment_0.path;
|
||||
let read_comment_views_top_path = CommentQueryBuilder::create(&conn)
|
||||
.post_id(inserted_post.id)
|
||||
.parent_path(top_path)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
let child_path = inserted_comment_1.to_owned().path;
|
||||
let read_comment_views_child_path = CommentQueryBuilder::create(&conn)
|
||||
.post_id(inserted_post.id)
|
||||
.parent_path(child_path)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
// Make sure the comment parent-limited fetch is correct
|
||||
assert_eq!(6, read_comment_views_top_path.len());
|
||||
assert_eq!(4, read_comment_views_child_path.len());
|
||||
|
||||
// Make sure it contains the parent, but not the comment from the other tree
|
||||
let child_comments = read_comment_views_child_path
|
||||
.into_iter()
|
||||
.map(|c| c.comment)
|
||||
.collect::<Vec<Comment>>();
|
||||
assert!(child_comments.contains(&inserted_comment_1));
|
||||
assert!(!child_comments.contains(&inserted_comment_2));
|
||||
|
||||
let read_comment_views_top_max_depth = CommentQueryBuilder::create(&conn)
|
||||
.post_id(inserted_post.id)
|
||||
.max_depth(1)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
// Make sure a depth limited one only has the top comment
|
||||
assert_eq!(
|
||||
expected_comment_view_no_person,
|
||||
read_comment_views_top_max_depth[0]
|
||||
);
|
||||
assert_eq!(1, read_comment_views_top_max_depth.len());
|
||||
|
||||
let child_path = inserted_comment_1.path;
|
||||
let read_comment_views_parent_max_depth = CommentQueryBuilder::create(&conn)
|
||||
.post_id(inserted_post.id)
|
||||
.parent_path(child_path)
|
||||
.max_depth(1)
|
||||
.sort(CommentSortType::New)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
// Make sure a depth limited one, and given child comment 1, has 3
|
||||
assert!(read_comment_views_parent_max_depth[2]
|
||||
.comment
|
||||
.content
|
||||
.eq("Comment 3"));
|
||||
assert_eq!(3, read_comment_views_parent_max_depth.len());
|
||||
|
||||
// Delete everything
|
||||
let like_removed =
|
||||
CommentLike::remove(&conn, inserted_person.id, inserted_comment_0.id).unwrap();
|
||||
let num_deleted = Comment::delete(&conn, inserted_comment_0.id).unwrap();
|
||||
Comment::delete(&conn, inserted_comment_1.id).unwrap();
|
||||
Post::delete(&conn, inserted_post.id).unwrap();
|
||||
Community::delete(&conn, inserted_community.id).unwrap();
|
||||
Person::delete(&conn, inserted_person.id).unwrap();
|
||||
Person::delete(&conn, inserted_person_2.id).unwrap();
|
||||
|
||||
assert_eq!(1, num_deleted);
|
||||
assert_eq!(1, like_removed);
|
||||
}
|
||||
|
|
|
@ -429,6 +429,7 @@ impl<'a> PostQueryBuilder<'a> {
|
|||
.then_order_by(hot_rank(post_aggregates::score, post_aggregates::published).desc())
|
||||
.then_order_by(post_aggregates::published.desc()),
|
||||
SortType::New => query.then_order_by(post_aggregates::published.desc()),
|
||||
SortType::Old => query.then_order_by(post_aggregates::published.asc()),
|
||||
SortType::NewComments => query.then_order_by(post_aggregates::newest_comment_time.desc()),
|
||||
SortType::MostComments => query
|
||||
.then_order_by(post_aggregates::comments.desc())
|
||||
|
|
|
@ -34,7 +34,6 @@ pub struct CommentReportView {
|
|||
pub struct CommentView {
|
||||
pub comment: Comment,
|
||||
pub creator: PersonSafe,
|
||||
pub recipient: Option<PersonSafeAlias1>, // Left joins to comment and person
|
||||
pub post: Post,
|
||||
pub community: CommunitySafe,
|
||||
pub counts: CommentAggregates,
|
||||
|
|
344
crates/db_views_actor/src/comment_reply_view.rs
Normal file
344
crates/db_views_actor/src/comment_reply_view.rs
Normal file
|
@ -0,0 +1,344 @@
|
|||
use crate::structs::CommentReplyView;
|
||||
use diesel::{dsl::*, result::Error, *};
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::CommentAggregates,
|
||||
newtypes::{CommentReplyId, PersonId},
|
||||
schema::{
|
||||
comment,
|
||||
comment_aggregates,
|
||||
comment_like,
|
||||
comment_reply,
|
||||
comment_saved,
|
||||
community,
|
||||
community_follower,
|
||||
community_person_ban,
|
||||
person,
|
||||
person_alias_1,
|
||||
person_block,
|
||||
post,
|
||||
},
|
||||
source::{
|
||||
comment::{Comment, CommentSaved},
|
||||
comment_reply::CommentReply,
|
||||
community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
|
||||
person::{Person, PersonAlias1, PersonSafe, PersonSafeAlias1},
|
||||
person_block::PersonBlock,
|
||||
post::Post,
|
||||
},
|
||||
traits::{MaybeOptional, ToSafe, ViewToVec},
|
||||
utils::{functions::hot_rank, limit_and_offset},
|
||||
CommentSortType,
|
||||
};
|
||||
|
||||
type CommentReplyViewTuple = (
|
||||
CommentReply,
|
||||
Comment,
|
||||
PersonSafe,
|
||||
Post,
|
||||
CommunitySafe,
|
||||
PersonSafeAlias1,
|
||||
CommentAggregates,
|
||||
Option<CommunityPersonBan>,
|
||||
Option<CommunityFollower>,
|
||||
Option<CommentSaved>,
|
||||
Option<PersonBlock>,
|
||||
Option<i16>,
|
||||
);
|
||||
|
||||
impl CommentReplyView {
|
||||
pub fn read(
|
||||
conn: &PgConnection,
|
||||
comment_reply_id: CommentReplyId,
|
||||
my_person_id: Option<PersonId>,
|
||||
) -> Result<Self, Error> {
|
||||
// The left join below will return None in this case
|
||||
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
|
||||
|
||||
let (
|
||||
comment_reply,
|
||||
comment,
|
||||
creator,
|
||||
post,
|
||||
community,
|
||||
recipient,
|
||||
counts,
|
||||
creator_banned_from_community,
|
||||
follower,
|
||||
saved,
|
||||
creator_blocked,
|
||||
my_vote,
|
||||
) = comment_reply::table
|
||||
.find(comment_reply_id)
|
||||
.inner_join(comment::table)
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
.inner_join(post::table.on(comment::post_id.eq(post::id)))
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(person_alias_1::table)
|
||||
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
|
||||
.left_join(
|
||||
community_person_ban::table.on(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(comment::creator_id))
|
||||
.and(
|
||||
community_person_ban::expires
|
||||
.is_null()
|
||||
.or(community_person_ban::expires.gt(now)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
community_follower::table.on(
|
||||
post::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
comment_saved::table.on(
|
||||
comment::id
|
||||
.eq(comment_saved::comment_id)
|
||||
.and(comment_saved::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
person_block::table.on(
|
||||
comment::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
comment_like::table.on(
|
||||
comment::id
|
||||
.eq(comment_like::comment_id)
|
||||
.and(comment_like::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.select((
|
||||
comment_reply::all_columns,
|
||||
comment::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
post::all_columns,
|
||||
Community::safe_columns_tuple(),
|
||||
PersonAlias1::safe_columns_tuple(),
|
||||
comment_aggregates::all_columns,
|
||||
community_person_ban::all_columns.nullable(),
|
||||
community_follower::all_columns.nullable(),
|
||||
comment_saved::all_columns.nullable(),
|
||||
person_block::all_columns.nullable(),
|
||||
comment_like::score.nullable(),
|
||||
))
|
||||
.first::<CommentReplyViewTuple>(conn)?;
|
||||
|
||||
Ok(CommentReplyView {
|
||||
comment_reply,
|
||||
comment,
|
||||
creator,
|
||||
post,
|
||||
community,
|
||||
recipient,
|
||||
counts,
|
||||
creator_banned_from_community: creator_banned_from_community.is_some(),
|
||||
subscribed: CommunityFollower::to_subscribed_type(&follower),
|
||||
saved: saved.is_some(),
|
||||
creator_blocked: creator_blocked.is_some(),
|
||||
my_vote,
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the number of unread replies
|
||||
pub fn get_unread_replies(conn: &PgConnection, my_person_id: PersonId) -> Result<i64, Error> {
|
||||
use diesel::dsl::*;
|
||||
|
||||
comment_reply::table
|
||||
.filter(comment_reply::recipient_id.eq(my_person_id))
|
||||
.filter(comment_reply::read.eq(false))
|
||||
.select(count(comment_reply::id))
|
||||
.first::<i64>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CommentReplyQueryBuilder<'a> {
|
||||
conn: &'a PgConnection,
|
||||
my_person_id: Option<PersonId>,
|
||||
recipient_id: Option<PersonId>,
|
||||
sort: Option<CommentSortType>,
|
||||
unread_only: Option<bool>,
|
||||
show_bot_accounts: Option<bool>,
|
||||
page: Option<i64>,
|
||||
limit: Option<i64>,
|
||||
}
|
||||
|
||||
impl<'a> CommentReplyQueryBuilder<'a> {
|
||||
pub fn create(conn: &'a PgConnection) -> Self {
|
||||
CommentReplyQueryBuilder {
|
||||
conn,
|
||||
my_person_id: None,
|
||||
recipient_id: None,
|
||||
sort: None,
|
||||
unread_only: None,
|
||||
show_bot_accounts: None,
|
||||
page: None,
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort<T: MaybeOptional<CommentSortType>>(mut self, sort: T) -> Self {
|
||||
self.sort = sort.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn unread_only<T: MaybeOptional<bool>>(mut self, unread_only: T) -> Self {
|
||||
self.unread_only = unread_only.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn show_bot_accounts<T: MaybeOptional<bool>>(mut self, show_bot_accounts: T) -> Self {
|
||||
self.show_bot_accounts = show_bot_accounts.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn recipient_id<T: MaybeOptional<PersonId>>(mut self, recipient_id: T) -> Self {
|
||||
self.recipient_id = recipient_id.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn my_person_id<T: MaybeOptional<PersonId>>(mut self, my_person_id: T) -> Self {
|
||||
self.my_person_id = my_person_id.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
|
||||
self.page = page.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
|
||||
self.limit = limit.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<CommentReplyView>, Error> {
|
||||
use diesel::dsl::*;
|
||||
|
||||
// The left join below will return None in this case
|
||||
let person_id_join = self.my_person_id.unwrap_or(PersonId(-1));
|
||||
|
||||
let mut query = comment_reply::table
|
||||
.inner_join(comment::table)
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
.inner_join(post::table.on(comment::post_id.eq(post::id)))
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(person_alias_1::table)
|
||||
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
|
||||
.left_join(
|
||||
community_person_ban::table.on(
|
||||
community::id
|
||||
.eq(community_person_ban::community_id)
|
||||
.and(community_person_ban::person_id.eq(comment::creator_id))
|
||||
.and(
|
||||
community_person_ban::expires
|
||||
.is_null()
|
||||
.or(community_person_ban::expires.gt(now)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
community_follower::table.on(
|
||||
post::community_id
|
||||
.eq(community_follower::community_id)
|
||||
.and(community_follower::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
comment_saved::table.on(
|
||||
comment::id
|
||||
.eq(comment_saved::comment_id)
|
||||
.and(comment_saved::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
person_block::table.on(
|
||||
comment::creator_id
|
||||
.eq(person_block::target_id)
|
||||
.and(person_block::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.left_join(
|
||||
comment_like::table.on(
|
||||
comment::id
|
||||
.eq(comment_like::comment_id)
|
||||
.and(comment_like::person_id.eq(person_id_join)),
|
||||
),
|
||||
)
|
||||
.select((
|
||||
comment_reply::all_columns,
|
||||
comment::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
post::all_columns,
|
||||
Community::safe_columns_tuple(),
|
||||
PersonAlias1::safe_columns_tuple(),
|
||||
comment_aggregates::all_columns,
|
||||
community_person_ban::all_columns.nullable(),
|
||||
community_follower::all_columns.nullable(),
|
||||
comment_saved::all_columns.nullable(),
|
||||
person_block::all_columns.nullable(),
|
||||
comment_like::score.nullable(),
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
if let Some(recipient_id) = self.recipient_id {
|
||||
query = query.filter(comment_reply::recipient_id.eq(recipient_id));
|
||||
}
|
||||
|
||||
if self.unread_only.unwrap_or(false) {
|
||||
query = query.filter(comment_reply::read.eq(false));
|
||||
}
|
||||
|
||||
if !self.show_bot_accounts.unwrap_or(true) {
|
||||
query = query.filter(person::bot_account.eq(false));
|
||||
};
|
||||
|
||||
query = match self.sort.unwrap_or(CommentSortType::Hot) {
|
||||
CommentSortType::Hot => query
|
||||
.then_order_by(hot_rank(comment_aggregates::score, comment_aggregates::published).desc())
|
||||
.then_order_by(comment_aggregates::published.desc()),
|
||||
CommentSortType::New => query.then_order_by(comment::published.desc()),
|
||||
CommentSortType::Old => query.then_order_by(comment::published.asc()),
|
||||
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
|
||||
};
|
||||
|
||||
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
|
||||
|
||||
let res = query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load::<CommentReplyViewTuple>(self.conn)?;
|
||||
|
||||
Ok(CommentReplyView::from_tuple_to_vec(res))
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewToVec for CommentReplyView {
|
||||
type DbTuple = CommentReplyViewTuple;
|
||||
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
|
||||
items
|
||||
.into_iter()
|
||||
.map(|a| Self {
|
||||
comment_reply: a.0,
|
||||
comment: a.1,
|
||||
creator: a.2,
|
||||
post: a.3,
|
||||
community: a.4,
|
||||
recipient: a.5,
|
||||
counts: a.6,
|
||||
creator_banned_from_community: a.7.is_some(),
|
||||
subscribed: CommunityFollower::to_subscribed_type(&a.8),
|
||||
saved: a.9.is_some(),
|
||||
creator_blocked: a.10.is_some(),
|
||||
my_vote: a.11,
|
||||
})
|
||||
.collect::<Vec<Self>>()
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
#[cfg(feature = "full")]
|
||||
pub mod comment_reply_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod community_block_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod community_follower_view;
|
||||
|
|
|
@ -27,7 +27,7 @@ use lemmy_db_schema::{
|
|||
},
|
||||
traits::{MaybeOptional, ToSafe, ViewToVec},
|
||||
utils::{functions::hot_rank, limit_and_offset},
|
||||
SortType,
|
||||
CommentSortType,
|
||||
};
|
||||
|
||||
type PersonMentionViewTuple = (
|
||||
|
@ -163,8 +163,9 @@ pub struct PersonMentionQueryBuilder<'a> {
|
|||
conn: &'a PgConnection,
|
||||
my_person_id: Option<PersonId>,
|
||||
recipient_id: Option<PersonId>,
|
||||
sort: Option<SortType>,
|
||||
sort: Option<CommentSortType>,
|
||||
unread_only: Option<bool>,
|
||||
show_bot_accounts: Option<bool>,
|
||||
page: Option<i64>,
|
||||
limit: Option<i64>,
|
||||
}
|
||||
|
@ -177,12 +178,13 @@ impl<'a> PersonMentionQueryBuilder<'a> {
|
|||
recipient_id: None,
|
||||
sort: None,
|
||||
unread_only: None,
|
||||
show_bot_accounts: None,
|
||||
page: None,
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort<T: MaybeOptional<SortType>>(mut self, sort: T) -> Self {
|
||||
pub fn sort<T: MaybeOptional<CommentSortType>>(mut self, sort: T) -> Self {
|
||||
self.sort = sort.get_optional();
|
||||
self
|
||||
}
|
||||
|
@ -192,6 +194,11 @@ impl<'a> PersonMentionQueryBuilder<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn show_bot_accounts<T: MaybeOptional<bool>>(mut self, show_bot_accounts: T) -> Self {
|
||||
self.show_bot_accounts = show_bot_accounts.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn recipient_id<T: MaybeOptional<PersonId>>(mut self, recipient_id: T) -> Self {
|
||||
self.recipient_id = recipient_id.get_optional();
|
||||
self
|
||||
|
@ -289,26 +296,17 @@ impl<'a> PersonMentionQueryBuilder<'a> {
|
|||
query = query.filter(person_mention::read.eq(false));
|
||||
}
|
||||
|
||||
query = match self.sort.unwrap_or(SortType::Hot) {
|
||||
SortType::Hot | SortType::Active => query
|
||||
.order_by(hot_rank(comment_aggregates::score, comment_aggregates::published).desc())
|
||||
if !self.show_bot_accounts.unwrap_or(true) {
|
||||
query = query.filter(person::bot_account.eq(false));
|
||||
};
|
||||
|
||||
query = match self.sort.unwrap_or(CommentSortType::Hot) {
|
||||
CommentSortType::Hot => query
|
||||
.then_order_by(hot_rank(comment_aggregates::score, comment_aggregates::published).desc())
|
||||
.then_order_by(comment_aggregates::published.desc()),
|
||||
SortType::New | SortType::MostComments | SortType::NewComments => {
|
||||
query.order_by(comment::published.desc())
|
||||
}
|
||||
SortType::TopAll => query.order_by(comment_aggregates::score.desc()),
|
||||
SortType::TopYear => query
|
||||
.filter(comment::published.gt(now - 1.years()))
|
||||
.order_by(comment_aggregates::score.desc()),
|
||||
SortType::TopMonth => query
|
||||
.filter(comment::published.gt(now - 1.months()))
|
||||
.order_by(comment_aggregates::score.desc()),
|
||||
SortType::TopWeek => query
|
||||
.filter(comment::published.gt(now - 1.weeks()))
|
||||
.order_by(comment_aggregates::score.desc()),
|
||||
SortType::TopDay => query
|
||||
.filter(comment::published.gt(now - 1.days()))
|
||||
.order_by(comment_aggregates::score.desc()),
|
||||
CommentSortType::New => query.then_order_by(comment::published.desc()),
|
||||
CommentSortType::Old => query.then_order_by(comment::published.asc()),
|
||||
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
|
||||
};
|
||||
|
||||
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
|
||||
|
|
|
@ -109,6 +109,7 @@ impl<'a> PersonQueryBuilder<'a> {
|
|||
SortType::New | SortType::MostComments | SortType::NewComments => {
|
||||
query.order_by(person::published.desc())
|
||||
}
|
||||
SortType::Old => query.order_by(person::published.asc()),
|
||||
SortType::TopAll => query.order_by(person_aggregates::comment_score.desc()),
|
||||
SortType::TopYear => query
|
||||
.filter(person::published.gt(now - 1.years()))
|
||||
|
|
|
@ -2,6 +2,7 @@ use lemmy_db_schema::{
|
|||
aggregates::structs::{CommentAggregates, CommunityAggregates, PersonAggregates},
|
||||
source::{
|
||||
comment::Comment,
|
||||
comment_reply::CommentReply,
|
||||
community::CommunitySafe,
|
||||
person::{PersonSafe, PersonSafeAlias1},
|
||||
person_mention::PersonMention,
|
||||
|
@ -65,6 +66,22 @@ pub struct PersonMentionView {
|
|||
pub my_vote: Option<i16>, // Left join to CommentLike
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
pub struct CommentReplyView {
|
||||
pub comment_reply: CommentReply,
|
||||
pub comment: Comment,
|
||||
pub creator: PersonSafe,
|
||||
pub post: Post,
|
||||
pub community: CommunitySafe,
|
||||
pub recipient: PersonSafeAlias1,
|
||||
pub counts: CommentAggregates,
|
||||
pub creator_banned_from_community: bool, // Left Join to CommunityPersonBan
|
||||
pub subscribed: SubscribedType, // Left join to CommunityFollower
|
||||
pub saved: bool, // Left join to CommentSaved
|
||||
pub creator_blocked: bool, // Left join to PersonBlock
|
||||
pub my_vote: Option<i16>, // Left join to CommentLike
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PersonViewSafe {
|
||||
pub person: PersonSafe,
|
||||
|
|
|
@ -7,17 +7,18 @@ use lemmy_db_schema::{
|
|||
newtypes::LocalUserId,
|
||||
source::{community::Community, local_user::LocalUser, person::Person},
|
||||
traits::{ApubActor, Crud},
|
||||
CommentSortType,
|
||||
ListingType,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::{
|
||||
comment_view::CommentQueryBuilder,
|
||||
post_view::PostQueryBuilder,
|
||||
structs::{CommentView, PostView, SiteView},
|
||||
structs::{PostView, SiteView},
|
||||
};
|
||||
use lemmy_db_views_actor::{
|
||||
comment_reply_view::CommentReplyQueryBuilder,
|
||||
person_mention_view::PersonMentionQueryBuilder,
|
||||
structs::PersonMentionView,
|
||||
structs::{CommentReplyView, PersonMentionView},
|
||||
};
|
||||
use lemmy_utils::{claims::Claims, error::LemmyError, utils::markdown_to_html};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
@ -284,9 +285,9 @@ fn get_feed_inbox(
|
|||
let person_id = local_user.person_id;
|
||||
let show_bot_accounts = local_user.show_bot_accounts;
|
||||
|
||||
let sort = SortType::New;
|
||||
let sort = CommentSortType::New;
|
||||
|
||||
let replies = CommentQueryBuilder::create(conn)
|
||||
let replies = CommentReplyQueryBuilder::create(conn)
|
||||
.recipient_id(person_id)
|
||||
.my_person_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
|
@ -297,6 +298,7 @@ fn get_feed_inbox(
|
|||
let mentions = PersonMentionQueryBuilder::create(conn)
|
||||
.recipient_id(person_id)
|
||||
.my_person_id(person_id)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.sort(sort)
|
||||
.limit(RSS_FETCH_LIMIT)
|
||||
.list()?;
|
||||
|
@ -319,7 +321,7 @@ fn get_feed_inbox(
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn create_reply_and_mention_items(
|
||||
replies: Vec<CommentView>,
|
||||
replies: Vec<CommentReplyView>,
|
||||
mentions: Vec<PersonMentionView>,
|
||||
protocol_and_hostname: &str,
|
||||
) -> Result<Vec<Item>, LemmyError> {
|
||||
|
|
|
@ -440,7 +440,7 @@ impl ChatServer {
|
|||
|
||||
fn sendit(&self, message: &str, id: ConnectionId) {
|
||||
if let Some(info) = self.sessions.get(&id) {
|
||||
let _ = info.addr.do_send(WsMessage(message.to_owned()));
|
||||
info.addr.do_send(WsMessage(message.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,6 @@ where
|
|||
pub enum UserOperation {
|
||||
Login,
|
||||
GetCaptcha,
|
||||
MarkCommentAsRead,
|
||||
SaveComment,
|
||||
CreateCommentLike,
|
||||
CreateCommentReport,
|
||||
|
@ -116,6 +115,7 @@ pub enum UserOperation {
|
|||
GetReplies,
|
||||
GetPersonMentions,
|
||||
MarkPersonMentionAsRead,
|
||||
MarkCommentReplyAsRead,
|
||||
GetModlog,
|
||||
BanFromCommunity,
|
||||
AddModToCommunity,
|
||||
|
|
|
@ -14,6 +14,7 @@ use lemmy_db_schema::{
|
|||
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId},
|
||||
source::{
|
||||
comment::Comment,
|
||||
comment_reply::{CommentReply, CommentReplyForm},
|
||||
person::Person,
|
||||
person_mention::{PersonMention, PersonMentionForm},
|
||||
post::Post,
|
||||
|
@ -223,71 +224,97 @@ pub async fn send_local_notifs(
|
|||
}
|
||||
}
|
||||
|
||||
// Send notifs to the parent commenter / poster
|
||||
match comment.parent_id {
|
||||
Some(parent_id) => {
|
||||
let parent_comment =
|
||||
blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await?;
|
||||
if let Ok(parent_comment) = parent_comment {
|
||||
// Get the parent commenter local_user
|
||||
let parent_creator_id = parent_comment.creator_id;
|
||||
// Send comment_reply to the parent commenter / poster
|
||||
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
||||
let parent_comment = blocking(context.pool(), move |conn| {
|
||||
Comment::read(conn, parent_comment_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Only add to recipients if that person isn't blocked
|
||||
let creator_blocked = check_person_block(person.id, parent_creator_id, context.pool())
|
||||
.await
|
||||
.is_err();
|
||||
// Get the parent commenter local_user
|
||||
let parent_creator_id = parent_comment.creator_id;
|
||||
|
||||
// Don't send a notif to yourself
|
||||
if parent_comment.creator_id != person.id && !creator_blocked {
|
||||
let user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read_person(conn, parent_creator_id)
|
||||
})
|
||||
.await?;
|
||||
if let Ok(parent_user_view) = user_view {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
// Only add to recipients if that person isn't blocked
|
||||
let creator_blocked = check_person_block(person.id, parent_creator_id, context.pool())
|
||||
.await
|
||||
.is_err();
|
||||
|
||||
if do_send_email {
|
||||
let lang = get_user_lang(&parent_user_view);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_comment_reply_subject(&person.name),
|
||||
&lang.notification_comment_reply_body(&comment.content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Its a post
|
||||
// Don't send a notif to yourself
|
||||
None => {
|
||||
// Only add to recipients if that person isn't blocked
|
||||
let creator_blocked = check_person_block(person.id, post.creator_id, context.pool())
|
||||
.await
|
||||
.is_err();
|
||||
if parent_comment.creator_id != person.id && !creator_blocked {
|
||||
let user_view = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read_person(conn, parent_creator_id)
|
||||
})
|
||||
.await?;
|
||||
if let Ok(parent_user_view) = user_view {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
||||
if post.creator_id != person.id && !creator_blocked {
|
||||
let creator_id = post.creator_id;
|
||||
let parent_user = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read_person(conn, creator_id)
|
||||
let comment_reply_form = CommentReplyForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommentReply::create(conn, &comment_reply_form)
|
||||
})
|
||||
.await?;
|
||||
if let Ok(parent_user_view) = parent_user {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
.await?
|
||||
.ok();
|
||||
|
||||
if do_send_email {
|
||||
let lang = get_user_lang(&parent_user_view);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_post_reply_subject(&person.name),
|
||||
&lang.notification_post_reply_body(&comment.content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
}
|
||||
if do_send_email {
|
||||
let lang = get_user_lang(&parent_user_view);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_comment_reply_subject(&person.name),
|
||||
&lang.notification_comment_reply_body(&comment.content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// If there's no parent, its the post creator
|
||||
// Only add to recipients if that person isn't blocked
|
||||
let creator_blocked = check_person_block(person.id, post.creator_id, context.pool())
|
||||
.await
|
||||
.is_err();
|
||||
|
||||
if post.creator_id != person.id && !creator_blocked {
|
||||
let creator_id = post.creator_id;
|
||||
let parent_user = blocking(context.pool(), move |conn| {
|
||||
LocalUserView::read_person(conn, creator_id)
|
||||
})
|
||||
.await?;
|
||||
if let Ok(parent_user_view) = parent_user {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
||||
let comment_reply_form = CommentReplyForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
blocking(context.pool(), move |conn| {
|
||||
CommentReply::create(conn, &comment_reply_form)
|
||||
})
|
||||
.await?
|
||||
.ok();
|
||||
|
||||
if do_send_email {
|
||||
let lang = get_user_lang(&parent_user_view);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_post_reply_subject(&person.name),
|
||||
&lang.notification_post_reply_body(&comment.content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(recipient_ids)
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ services:
|
|||
- lemmy
|
||||
|
||||
postgres:
|
||||
image: postgres:12-alpine
|
||||
image: postgres:14-alpine
|
||||
ports:
|
||||
# use a different port so it doesnt conflict with postgres running on the host
|
||||
- "5433:5432"
|
||||
|
|
|
@ -47,7 +47,7 @@ services:
|
|||
ports:
|
||||
- "8541:8541"
|
||||
postgres_alpha:
|
||||
image: postgres:12-alpine
|
||||
image: postgres:14-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=lemmy
|
||||
- POSTGRES_PASSWORD=password
|
||||
|
@ -75,7 +75,7 @@ services:
|
|||
ports:
|
||||
- "8551:8551"
|
||||
postgres_beta:
|
||||
image: postgres:12-alpine
|
||||
image: postgres:14-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=lemmy
|
||||
- POSTGRES_PASSWORD=password
|
||||
|
@ -103,7 +103,7 @@ services:
|
|||
ports:
|
||||
- "8561:8561"
|
||||
postgres_gamma:
|
||||
image: postgres:12-alpine
|
||||
image: postgres:14-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=lemmy
|
||||
- POSTGRES_PASSWORD=password
|
||||
|
@ -132,7 +132,7 @@ services:
|
|||
ports:
|
||||
- "8571:8571"
|
||||
postgres_delta:
|
||||
image: postgres:12-alpine
|
||||
image: postgres:14-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=lemmy
|
||||
- POSTGRES_PASSWORD=password
|
||||
|
@ -161,7 +161,7 @@ services:
|
|||
ports:
|
||||
- "8581:8581"
|
||||
postgres_epsilon:
|
||||
image: postgres:12-alpine
|
||||
image: postgres:14-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=lemmy
|
||||
- POSTGRES_PASSWORD=password
|
||||
|
|
|
@ -2,7 +2,7 @@ version: '2.2'
|
|||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:12-alpine
|
||||
image: postgres:14-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=lemmy
|
||||
- POSTGRES_PASSWORD=password
|
||||
|
|
25
migrations/2022-07-07-182650_comment_ltrees/down.sql
Normal file
25
migrations/2022-07-07-182650_comment_ltrees/down.sql
Normal file
|
@ -0,0 +1,25 @@
|
|||
alter table comment add column parent_id integer;
|
||||
|
||||
-- Constraints and index
|
||||
alter table comment add constraint comment_parent_id_fkey foreign key (parent_id) REFERENCES comment(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
create index idx_comment_parent on comment (parent_id);
|
||||
|
||||
-- Update the parent_id column
|
||||
-- subpath(subpath(0, -1), -1) gets the immediate parent but it fails null checks
|
||||
update comment set parent_id = cast(ltree2text(nullif(subpath(nullif(subpath(path, 0, -1), '0'), -1), '0')) as INTEGER);
|
||||
|
||||
alter table comment drop column path;
|
||||
alter table comment_aggregates drop column child_count;
|
||||
|
||||
drop extension ltree;
|
||||
|
||||
-- Add back in the read column
|
||||
alter table comment add column read boolean default false not null;
|
||||
|
||||
update comment c set read = cr.read
|
||||
from comment_reply cr where cr.comment_id = c.id;
|
||||
|
||||
create view comment_alias_1 as select * from comment;
|
||||
|
||||
drop table comment_reply;
|
||||
|
83
migrations/2022-07-07-182650_comment_ltrees/up.sql
Normal file
83
migrations/2022-07-07-182650_comment_ltrees/up.sql
Normal file
|
@ -0,0 +1,83 @@
|
|||
|
||||
-- Remove the comment.read column, and create a new comment_reply table,
|
||||
-- similar to the person_mention table.
|
||||
--
|
||||
-- This is necessary because self-joins using ltrees would be too tough with SQL views
|
||||
--
|
||||
-- Every comment should have a row here, because all comments have a recipient,
|
||||
-- either the post creator, or the parent commenter.
|
||||
create table comment_reply(
|
||||
id serial primary key,
|
||||
recipient_id int references person on update cascade on delete cascade not null,
|
||||
comment_id int references comment on update cascade on delete cascade not null,
|
||||
read boolean default false not null,
|
||||
published timestamp not null default now(),
|
||||
unique(recipient_id, comment_id)
|
||||
);
|
||||
|
||||
-- Ones where parent_id is null, use the post creator recipient
|
||||
insert into comment_reply (recipient_id, comment_id, read)
|
||||
select p.creator_id, c.id, c.read from comment c
|
||||
inner join post p on c.post_id = p.id
|
||||
where c.parent_id is null;
|
||||
|
||||
-- Ones where there is a parent_id, self join to comment to get the parent comment creator
|
||||
insert into comment_reply (recipient_id, comment_id, read)
|
||||
select c2.creator_id, c.id, c.read from comment c
|
||||
inner join comment c2 on c.parent_id = c2.id;
|
||||
|
||||
-- Drop comment_alias view
|
||||
drop view comment_alias_1;
|
||||
|
||||
alter table comment drop column read;
|
||||
|
||||
create extension ltree;
|
||||
|
||||
alter table comment add column path ltree not null default '0';
|
||||
alter table comment_aggregates add column child_count integer not null default 0;
|
||||
|
||||
-- The ltree path column should be the comment_id parent paths, separated by dots.
|
||||
-- Stackoverflow: building an ltree from a parent_id hierarchical tree:
|
||||
-- https://stackoverflow.com/a/1144848/1655478
|
||||
|
||||
create temporary table comment_temp as
|
||||
WITH RECURSIVE q AS (
|
||||
SELECT h, 1 AS level, ARRAY[id] AS breadcrumb
|
||||
FROM comment h
|
||||
WHERE parent_id is null
|
||||
UNION ALL
|
||||
SELECT hi, q.level + 1 AS level, breadcrumb || id
|
||||
FROM q
|
||||
JOIN comment hi
|
||||
ON hi.parent_id = (q.h).id
|
||||
)
|
||||
SELECT (q.h).id,
|
||||
(q.h).parent_id,
|
||||
level,
|
||||
breadcrumb::VARCHAR AS path,
|
||||
text2ltree('0.' || array_to_string(breadcrumb, '.')) as ltree_path
|
||||
FROM q
|
||||
ORDER BY
|
||||
breadcrumb;
|
||||
|
||||
-- Add the ltree column
|
||||
update comment c
|
||||
set path = ct.ltree_path
|
||||
from comment_temp ct
|
||||
where c.id = ct.id;
|
||||
|
||||
-- Update the child counts
|
||||
update comment_aggregates ca set child_count = c2.child_count
|
||||
from (
|
||||
select c.id, c.path, count(c2.id) as child_count from comment c
|
||||
left join comment c2 on c2.path <@ c.path and c2.path != c.path
|
||||
group by c.id
|
||||
) as c2
|
||||
where ca.comment_id = c2.id;
|
||||
|
||||
-- Create the index
|
||||
create index idx_path_gist on comment using gist (path);
|
||||
|
||||
-- Drop the parent_id column
|
||||
alter table comment drop column parent_id cascade;
|
||||
|
54
scripts/postgres_12_to_14_upgrade.sh
Executable file
54
scripts/postgres_12_to_14_upgrade.sh
Executable file
|
@ -0,0 +1,54 @@
|
|||
##!/bin/sh
|
||||
set -e
|
||||
|
||||
## This script upgrades the postgres from version 12 to 14
|
||||
|
||||
## Make sure everything is started
|
||||
sudo docker-compose start
|
||||
|
||||
# Export the DB
|
||||
echo "Exporting the Database to 12_14.dump.sql ..."
|
||||
sudo docker-compose exec -T postgres pg_dumpall -c -U lemmy > 12_14_dump.sql
|
||||
echo "Done."
|
||||
|
||||
# Stop everything
|
||||
sudo docker-compose stop
|
||||
|
||||
sleep 10s
|
||||
|
||||
# Delete the folder
|
||||
echo "Removing the old postgres folder"
|
||||
sudo rm -rf volumes/postgres
|
||||
|
||||
# Change the version in your docker-compose.yml
|
||||
echo "Updating docker-compose to use postgres version 14."
|
||||
sed -i "s/postgres:12-alpine/postgres:14-alpine/" ./docker-compose.yml
|
||||
|
||||
# Start up postgres
|
||||
echo "Starting up new postgres..."
|
||||
sudo docker-compose up -d postgres
|
||||
|
||||
# Sleep for a bit so it can start up, build the new folders
|
||||
sleep 20s
|
||||
|
||||
# Import the DB
|
||||
echo "Importing the database...."
|
||||
cat 12_14_dump.sql | sudo docker-compose exec -T postgres psql -U lemmy
|
||||
echo "Done."
|
||||
|
||||
POSTGRES_PASSWORD=$(grep "POSTGRES_PASSWORD" ./docker-compose.yml | cut -d"=" -f2)
|
||||
|
||||
# Fix weird password issue with postgres 14
|
||||
echo "Fixing a weird password issue with postgres 14"
|
||||
sudo docker-compose exec -T postgres psql -U lemmy -c "alter user lemmy with password '$POSTGRES_PASSWORD'"
|
||||
sudo docker-compose restart postgres
|
||||
|
||||
# Just in case
|
||||
sudo chown -R 991:991 volumes/pictrs
|
||||
|
||||
# Start up the rest of lemmy
|
||||
echo "Starting up lemmy"
|
||||
sudo docker-compose up -d
|
||||
|
||||
# Delete the DB Dump? Probably safe to keep it
|
||||
echo "A copy of your old database is at 12_14.dump.sql . You can delete this file if the upgrade went smoothly."
|
|
@ -10,3 +10,4 @@ export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy
|
|||
export LEMMY_CONFIG_LOCATION=../../config/config.hjson
|
||||
RUST_BACKTRACE=1 \
|
||||
cargo test --workspace --no-fail-fast
|
||||
# Add this to do printlns: -- --nocapture
|
||||
|
|
|
@ -119,7 +119,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
|||
.route("/remove", web::post().to(route_post_crud::<RemoveComment>))
|
||||
.route(
|
||||
"/mark_as_read",
|
||||
web::post().to(route_post::<MarkCommentAsRead>),
|
||||
web::post().to(route_post::<MarkCommentReplyAsRead>),
|
||||
)
|
||||
.route("/like", web::post().to(route_post::<CreateCommentLike>))
|
||||
.route("/save", web::put().to(route_post::<SaveComment>))
|
||||
|
|
Loading…
Reference in a new issue