mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-22 12:21:18 +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:
|
services:
|
||||||
- name: database
|
- name: database
|
||||||
image: postgres:12-alpine
|
image: postgres:14-alpine
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: lemmy
|
POSTGRES_USER: lemmy
|
||||||
POSTGRES_PASSWORD: password
|
POSTGRES_PASSWORD: password
|
||||||
|
|
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -834,12 +834,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.13.1"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4"
|
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core 0.13.1",
|
"darling_core 0.13.4",
|
||||||
"darling_macro 0.13.1",
|
"darling_macro 0.13.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -868,9 +868,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.13.1"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324"
|
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
"fnv",
|
||||||
"ident_case",
|
"ident_case",
|
||||||
|
@ -907,11 +907,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.13.1"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b"
|
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core 0.13.1",
|
"darling_core 0.13.4",
|
||||||
"quote 1.0.18",
|
"quote 1.0.18",
|
||||||
"syn 1.0.96",
|
"syn 1.0.96",
|
||||||
]
|
]
|
||||||
|
@ -1058,6 +1058,16 @@ dependencies = [
|
||||||
"syn 1.0.96",
|
"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]]
|
[[package]]
|
||||||
name = "diesel_migrations"
|
name = "diesel_migrations"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1132,7 +1142,7 @@ name = "doku-derive"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
source = "git+https://github.com/anixe/doku#10a0339a82be92b5f160aac325d11c9c2ef875e1"
|
source = "git+https://github.com/anixe/doku#10a0339a82be92b5f160aac325d11c9c2ef875e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling 0.13.1",
|
"darling 0.13.4",
|
||||||
"proc-macro2 1.0.39",
|
"proc-macro2 1.0.39",
|
||||||
"quote 1.0.18",
|
"quote 1.0.18",
|
||||||
"syn 1.0.96",
|
"syn 1.0.96",
|
||||||
|
@ -1984,6 +1994,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel-derive-newtype",
|
"diesel-derive-newtype",
|
||||||
|
"diesel_ltree",
|
||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"lemmy_utils",
|
"lemmy_utils",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -2002,6 +2013,7 @@ name = "lemmy_db_views"
|
||||||
version = "0.16.5"
|
version = "0.16.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"diesel",
|
"diesel",
|
||||||
|
"diesel_ltree",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
"serde",
|
"serde",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
|
@ -3507,22 +3519,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "1.12.0"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec1e6ec4d8950e5b1e894eac0d360742f3b1407a6078a604a731c4b3f49cefbc"
|
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustversion",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_with_macros",
|
"serde_with_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "1.5.1"
|
version = "1.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e"
|
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling 0.13.1",
|
"darling 0.13.4",
|
||||||
"proc-macro2 1.0.39",
|
"proc-macro2 1.0.39",
|
||||||
"quote 1.0.18",
|
"quote 1.0.18",
|
||||||
"syn 1.0.96",
|
"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"
|
"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": {
|
"devDependencies": {
|
||||||
|
"@sniptt/monads": "^0.5.10",
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
"eslint": "^7.30.0",
|
"class-transformer": "^0.5.1",
|
||||||
"eslint-plugin-jane": "^9.0.3",
|
"eslint": "^8.20.0",
|
||||||
|
"eslint-plugin-jane": "^11.2.2",
|
||||||
"jest": "^27.0.6",
|
"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",
|
"node-fetch": "^2.6.1",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.7.1",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
"ts-jest": "^27.0.3",
|
"ts-jest": "^27.0.3",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
jest.setTimeout(180000);
|
jest.setTimeout(180000);
|
||||||
|
import {None, Some} from '@sniptt/monads';
|
||||||
|
import { CommentView } from 'lemmy-js-client';
|
||||||
|
import { PostResponse } from 'lemmy-js-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
beta,
|
beta,
|
||||||
|
@ -24,10 +28,9 @@ import {
|
||||||
randomString,
|
randomString,
|
||||||
API,
|
API,
|
||||||
unfollows,
|
unfollows,
|
||||||
|
getComments,
|
||||||
|
getCommentParentId,
|
||||||
} from './shared';
|
} from './shared';
|
||||||
import { CommentView } from 'lemmy-js-client';
|
|
||||||
|
|
||||||
import { PostResponse } from 'lemmy-js-client';
|
|
||||||
|
|
||||||
let postRes: PostResponse;
|
let postRes: PostResponse;
|
||||||
|
|
||||||
|
@ -39,7 +42,7 @@ beforeAll(async () => {
|
||||||
let betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
let betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
||||||
postRes = await createPost(
|
postRes = await createPost(
|
||||||
alpha,
|
alpha,
|
||||||
betaCommunity.community.id
|
betaCommunity.unwrap().community.id
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,14 +65,14 @@ function assertCommentFederation(
|
||||||
}
|
}
|
||||||
|
|
||||||
test('Create a comment', async () => {
|
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.comment.content).toBeDefined();
|
||||||
expect(commentRes.comment_view.community.local).toBe(false);
|
expect(commentRes.comment_view.community.local).toBe(false);
|
||||||
expect(commentRes.comment_view.creator.local).toBe(true);
|
expect(commentRes.comment_view.creator.local).toBe(true);
|
||||||
expect(commentRes.comment_view.counts.score).toBe(1);
|
expect(commentRes.comment_view.counts.score).toBe(1);
|
||||||
|
|
||||||
// Make sure that comment is liked on beta
|
// 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).toBeDefined();
|
||||||
expect(betaComment.community.local).toBe(true);
|
expect(betaComment.community.local).toBe(true);
|
||||||
expect(betaComment.creator.local).toBe(false);
|
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 () => {
|
test('Create a comment in a non-existent post', async () => {
|
||||||
let commentRes = await createComment(alpha, -1);
|
let commentRes = await createComment(alpha, -1, None) as any;
|
||||||
expect(commentRes).toStrictEqual({ error: 'couldnt_find_post' });
|
expect(commentRes.error).toBe('couldnt_find_post');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Update a comment', async () => {
|
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
|
// Federate the comment first
|
||||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
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(
|
let updateCommentRes = await editComment(
|
||||||
alpha,
|
alpha,
|
||||||
|
@ -102,7 +105,7 @@ test('Update a comment', async () => {
|
||||||
let betaCommentUpdated = (await resolveComment(
|
let betaCommentUpdated = (await resolveComment(
|
||||||
beta,
|
beta,
|
||||||
commentRes.comment_view.comment
|
commentRes.comment_view.comment
|
||||||
)).comment;
|
)).comment.unwrap();
|
||||||
assertCommentFederation(
|
assertCommentFederation(
|
||||||
betaCommentUpdated,
|
betaCommentUpdated,
|
||||||
updateCommentRes.comment_view
|
updateCommentRes.comment_view
|
||||||
|
@ -110,7 +113,7 @@ test('Update a comment', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Delete 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(
|
let deleteCommentRes = await deleteComment(
|
||||||
alpha,
|
alpha,
|
||||||
|
@ -121,8 +124,8 @@ test('Delete a comment', async () => {
|
||||||
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
||||||
|
|
||||||
// Make sure that comment is undefined on beta
|
// Make sure that comment is undefined on beta
|
||||||
let betaCommentRes: any = await resolveComment(beta, commentRes.comment_view.comment);
|
let betaCommentRes = await resolveComment(beta, commentRes.comment_view.comment) as any;
|
||||||
expect(betaCommentRes).toStrictEqual({ error: 'couldnt_find_object' });
|
expect(betaCommentRes.error).toBe('couldnt_find_object');
|
||||||
|
|
||||||
let undeleteCommentRes = await deleteComment(
|
let undeleteCommentRes = await deleteComment(
|
||||||
alpha,
|
alpha,
|
||||||
|
@ -132,7 +135,7 @@ test('Delete a comment', async () => {
|
||||||
expect(undeleteCommentRes.comment_view.comment.deleted).toBe(false);
|
expect(undeleteCommentRes.comment_view.comment.deleted).toBe(false);
|
||||||
|
|
||||||
// Make sure that comment is undeleted on beta
|
// 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);
|
expect(betaComment2.comment.deleted).toBe(false);
|
||||||
assertCommentFederation(
|
assertCommentFederation(
|
||||||
betaComment2,
|
betaComment2,
|
||||||
|
@ -141,12 +144,12 @@ test('Delete a comment', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Remove a comment from admin and community on the same instance', 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
|
// Get the id for beta
|
||||||
let betaCommentId = (
|
let betaCommentId = (
|
||||||
await resolveComment(beta, commentRes.comment_view.comment)
|
await resolveComment(beta, commentRes.comment_view.comment)
|
||||||
).comment.comment.id;
|
).comment.unwrap().comment.id;
|
||||||
|
|
||||||
// The beta admin removes it (the community lives on beta)
|
// The beta admin removes it (the community lives on beta)
|
||||||
let removeCommentRes = await removeComment(beta, true, betaCommentId);
|
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("");
|
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)
|
// 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);
|
let refetchedPostComments = await getComments(alpha, postRes.post_view.post.id);
|
||||||
expect(refetchedPost.comments[0].comment.removed).toBe(true);
|
expect(refetchedPostComments.comments[0].comment.removed).toBe(true);
|
||||||
|
|
||||||
let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
|
let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
|
||||||
expect(unremoveCommentRes.comment_view.comment.removed).toBe(false);
|
expect(unremoveCommentRes.comment_view.comment.removed).toBe(false);
|
||||||
|
|
||||||
// Make sure that comment is unremoved on beta
|
// Make sure that comment is unremoved on beta
|
||||||
let refetchedPost2 = await getPost(alpha, postRes.post_view.post.id);
|
let refetchedPostComments2 = await getComments(alpha, postRes.post_view.post.id);
|
||||||
expect(refetchedPost2.comments[0].comment.removed).toBe(false);
|
expect(refetchedPostComments2.comments[0].comment.removed).toBe(false);
|
||||||
assertCommentFederation(
|
assertCommentFederation(
|
||||||
refetchedPost2.comments[0],
|
refetchedPostComments2.comments[0],
|
||||||
unremoveCommentRes.comment_view
|
unremoveCommentRes.comment_view
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -182,11 +185,11 @@ test('Remove a comment from admin and community on different instance', async ()
|
||||||
newAlphaApi,
|
newAlphaApi,
|
||||||
newCommunity.community_view.community.id
|
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();
|
expect(commentRes.comment_view.comment.content).toBeDefined();
|
||||||
|
|
||||||
// Beta searches that to cache it, then removes it
|
// 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(
|
let removeCommentRes = await removeComment(
|
||||||
beta,
|
beta,
|
||||||
true,
|
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);
|
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
|
||||||
|
|
||||||
// Make sure its not removed on alpha
|
// Make sure its not removed on alpha
|
||||||
let refetchedPost = await getPost(newAlphaApi, newPost.post_view.post.id);
|
let refetchedPostComments = await getComments(alpha, newPost.post_view.post.id);
|
||||||
expect(refetchedPost.comments[0].comment.removed).toBe(false);
|
expect(refetchedPostComments.comments[0].comment.removed).toBe(false);
|
||||||
assertCommentFederation(refetchedPost.comments[0], commentRes.comment_view);
|
assertCommentFederation(refetchedPostComments.comments[0], commentRes.comment_view);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unlike a comment', async () => {
|
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);
|
let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
|
||||||
expect(unlike.comment_view.counts.score).toBe(0);
|
expect(unlike.comment_view.counts.score).toBe(0);
|
||||||
|
|
||||||
// Make sure that post is unliked on beta
|
// 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).toBeDefined();
|
||||||
expect(betaComment.community.local).toBe(true);
|
expect(betaComment.community.local).toBe(true);
|
||||||
expect(betaComment.creator.local).toBe(false);
|
expect(betaComment.creator.local).toBe(false);
|
||||||
|
@ -214,23 +217,23 @@ test('Unlike a comment', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Federated comment like', 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
|
// 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);
|
let like = await likeComment(beta, 1, betaComment.comment);
|
||||||
expect(like.comment_view.counts.score).toBe(2);
|
expect(like.comment_view.counts.score).toBe(2);
|
||||||
|
|
||||||
// Get the post from alpha, check the likes
|
// Get the post from alpha, check the likes
|
||||||
let post = await getPost(alpha, postRes.post_view.post.id);
|
let postComments = await getComments(alpha, postRes.post_view.post.id);
|
||||||
expect(post.comments[0].counts.score).toBe(2);
|
expect(postComments.comments[0].counts.score).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Reply to a comment', async () => {
|
test('Reply to a comment', async () => {
|
||||||
// Create a comment on alpha, find it on beta
|
// Create a comment on alpha, find it on beta
|
||||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
|
||||||
|
|
||||||
// find that comment id on beta
|
// find that comment id on beta
|
||||||
|
|
||||||
|
@ -238,22 +241,22 @@ test('Reply to a comment', async () => {
|
||||||
let replyRes = await createComment(
|
let replyRes = await createComment(
|
||||||
beta,
|
beta,
|
||||||
betaComment.post.id,
|
betaComment.post.id,
|
||||||
betaComment.comment.id
|
Some(betaComment.comment.id)
|
||||||
);
|
);
|
||||||
expect(replyRes.comment_view.comment.content).toBeDefined();
|
expect(replyRes.comment_view.comment.content).toBeDefined();
|
||||||
expect(replyRes.comment_view.community.local).toBe(true);
|
expect(replyRes.comment_view.community.local).toBe(true);
|
||||||
expect(replyRes.comment_view.creator.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);
|
expect(replyRes.comment_view.counts.score).toBe(1);
|
||||||
|
|
||||||
// Make sure that comment is seen on alpha
|
// Make sure that comment is seen on alpha
|
||||||
// TODO not sure why, but a searchComment back to alpha, for the ap_id of betas
|
// TODO not sure why, but a searchComment back to alpha, for the ap_id of betas
|
||||||
// comment, isn't working.
|
// comment, isn't working.
|
||||||
// let searchAlpha = await searchComment(alpha, replyRes.comment);
|
// let searchAlpha = await searchComment(alpha, replyRes.comment);
|
||||||
let post = await getPost(alpha, postRes.post_view.post.id);
|
let postComments = await getComments(alpha, postRes.post_view.post.id);
|
||||||
let alphaComment = post.comments[0];
|
let alphaComment = postComments.comments[0];
|
||||||
expect(alphaComment.comment.content).toBeDefined();
|
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.community.local).toBe(false);
|
||||||
expect(alphaComment.creator.local).toBe(false);
|
expect(alphaComment.creator.local).toBe(false);
|
||||||
expect(alphaComment.counts.score).toBe(1);
|
expect(alphaComment.counts.score).toBe(1);
|
||||||
|
@ -263,11 +266,11 @@ test('Reply to a comment', async () => {
|
||||||
test('Mention beta', async () => {
|
test('Mention beta', async () => {
|
||||||
// Create a mention on alpha
|
// Create a mention on alpha
|
||||||
let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8551';
|
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(
|
let mentionRes = await createComment(
|
||||||
alpha,
|
alpha,
|
||||||
postRes.post_view.post.id,
|
postRes.post_view.post.id,
|
||||||
commentRes.comment_view.comment.id,
|
Some(commentRes.comment_view.comment.id),
|
||||||
mentionContent
|
mentionContent
|
||||||
);
|
);
|
||||||
expect(mentionRes.comment_view.comment.content).toBeDefined();
|
expect(mentionRes.comment_view.comment.content).toBeDefined();
|
||||||
|
@ -283,8 +286,8 @@ test('Mention beta', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Comment Search', async () => {
|
test('Comment Search', async () => {
|
||||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
|
||||||
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
|
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
|
||||||
assertCommentFederation(betaComment, commentRes.comment_view);
|
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);
|
expect(alphaPost.post_view.community.local).toBe(true);
|
||||||
|
|
||||||
// Make sure gamma sees it
|
// 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 =
|
let commentContent =
|
||||||
'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8551';
|
'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8551';
|
||||||
let commentRes = await createComment(
|
let commentRes = await createComment(
|
||||||
gamma,
|
gamma,
|
||||||
gammaPost.post.id,
|
gammaPost.post.id,
|
||||||
undefined,
|
None,
|
||||||
commentContent
|
commentContent
|
||||||
);
|
);
|
||||||
expect(commentRes.comment_view.comment.content).toBe(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);
|
expect(commentRes.comment_view.counts.score).toBe(1);
|
||||||
|
|
||||||
// Make sure alpha sees it
|
// Make sure alpha sees it
|
||||||
let alphaPost2 = await getPost(alpha, alphaPost.post_view.post.id);
|
let alphaPostComments2 = await getComments(alpha, alphaPost.post_view.post.id);
|
||||||
expect(alphaPost2.comments[0].comment.content).toBe(commentContent);
|
expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
|
||||||
expect(alphaPost2.comments[0].community.local).toBe(true);
|
expect(alphaPostComments2.comments[0].community.local).toBe(true);
|
||||||
expect(alphaPost2.comments[0].creator.local).toBe(false);
|
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
|
||||||
expect(alphaPost2.comments[0].counts.score).toBe(1);
|
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
|
||||||
assertCommentFederation(alphaPost2.comments[0], commentRes.comment_view);
|
assertCommentFederation(alphaPostComments2.comments[0], commentRes.comment_view);
|
||||||
|
|
||||||
// Make sure beta has mentions
|
// Make sure beta has mentions
|
||||||
let mentionsRes = await getMentions(beta);
|
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);
|
expect(betaPost.post_view.community.local).toBe(true);
|
||||||
|
|
||||||
// Make sure gamma and alpha see it
|
// 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();
|
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();
|
expect(alphaPost.post).toBeDefined();
|
||||||
|
|
||||||
// The bug: gamma comments, and alpha should see it.
|
// 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(
|
let commentRes = await createComment(
|
||||||
gamma,
|
gamma,
|
||||||
gammaPost.post.id,
|
gammaPost.post.id,
|
||||||
undefined,
|
None,
|
||||||
commentContent
|
commentContent
|
||||||
);
|
);
|
||||||
expect(commentRes.comment_view.comment.content).toBe(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);
|
expect(commentRes.comment_view.counts.score).toBe(1);
|
||||||
|
|
||||||
// Make sure alpha sees it
|
// Make sure alpha sees it
|
||||||
let alphaPost2 = await getPost(alpha, alphaPost.post.id);
|
let alphaPostComments2 = await getComments(alpha, alphaPost.post.id);
|
||||||
expect(alphaPost2.comments[0].comment.content).toBe(commentContent);
|
expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
|
||||||
expect(alphaPost2.comments[0].community.local).toBe(false);
|
expect(alphaPostComments2.comments[0].community.local).toBe(false);
|
||||||
expect(alphaPost2.comments[0].creator.local).toBe(false);
|
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
|
||||||
expect(alphaPost2.comments[0].counts.score).toBe(1);
|
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
|
||||||
assertCommentFederation(alphaPost2.comments[0], commentRes.comment_view);
|
assertCommentFederation(alphaPostComments2.comments[0], commentRes.comment_view);
|
||||||
|
|
||||||
await unfollowRemotes(alpha);
|
await unfollowRemotes(alpha);
|
||||||
await unfollowRemotes(gamma);
|
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
|
// Unfollow all remote communities
|
||||||
let site = await unfollowRemotes(alpha);
|
let site = await unfollowRemotes(alpha);
|
||||||
expect(
|
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);
|
).toBe(0);
|
||||||
|
|
||||||
// B creates a post, and two comments, should be invisible to A
|
// 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(
|
let parentCommentRes = await createComment(
|
||||||
beta,
|
beta,
|
||||||
postRes.post_view.post.id,
|
postRes.post_view.post.id,
|
||||||
undefined,
|
None,
|
||||||
parentCommentContent
|
parentCommentContent
|
||||||
);
|
);
|
||||||
expect(parentCommentRes.comment_view.comment.content).toBe(
|
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(
|
let childCommentRes = await createComment(
|
||||||
beta,
|
beta,
|
||||||
postRes.post_view.post.id,
|
postRes.post_view.post.id,
|
||||||
parentCommentRes.comment_view.comment.id,
|
Some(parentCommentRes.comment_view.comment.id),
|
||||||
childCommentContent
|
childCommentContent
|
||||||
);
|
);
|
||||||
expect(childCommentRes.comment_view.comment.content).toBe(
|
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);
|
expect(updateRes.comment_view.comment.content).toBe(updatedCommentContent);
|
||||||
|
|
||||||
// Get the post from alpha
|
// 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 alphaPost = await getPost(alpha, alphaPostB.post.id);
|
||||||
|
let alphaPostComments = await getComments(alpha, alphaPostB.post.id);
|
||||||
expect(alphaPost.post_view.post.name).toBeDefined();
|
expect(alphaPost.post_view.post.name).toBeDefined();
|
||||||
assertCommentFederation(alphaPost.comments[1], parentCommentRes.comment_view);
|
assertCommentFederation(alphaPostComments.comments[1], parentCommentRes.comment_view);
|
||||||
assertCommentFederation(alphaPost.comments[0], updateRes.comment_view);
|
assertCommentFederation(alphaPostComments.comments[0], updateRes.comment_view);
|
||||||
expect(alphaPost.post_view.community.local).toBe(false);
|
expect(alphaPost.post_view.community.local).toBe(false);
|
||||||
expect(alphaPost.post_view.creator.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 () => {
|
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;
|
let postRes = (await createPost(beta, betaCommunity.community.id)).post_view.post;
|
||||||
expect(postRes).toBeDefined();
|
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();
|
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)))
|
let alphaReport = (await reportComment(alpha, alphaComment.id, randomString(10)))
|
||||||
.comment_report_view.comment_report;
|
.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.original_comment_text).toBe(alphaReport.original_comment_text);
|
||||||
expect(betaReport.reason).toBe(alphaReport.reason);
|
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);
|
jest.setTimeout(120000);
|
||||||
|
import { CommunityView } from 'lemmy-js-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
beta,
|
beta,
|
||||||
|
@ -10,7 +12,6 @@ import {
|
||||||
getCommunity,
|
getCommunity,
|
||||||
followCommunity,
|
followCommunity,
|
||||||
} from './shared';
|
} from './shared';
|
||||||
import { CommunityView } from 'lemmy-js-client';
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await setupLogins();
|
await setupLogins();
|
||||||
|
@ -23,11 +24,11 @@ function assertCommunityFederation(
|
||||||
expect(communityOne.community.actor_id).toBe(communityTwo.community.actor_id);
|
expect(communityOne.community.actor_id).toBe(communityTwo.community.actor_id);
|
||||||
expect(communityOne.community.name).toBe(communityTwo.community.name);
|
expect(communityOne.community.name).toBe(communityTwo.community.name);
|
||||||
expect(communityOne.community.title).toBe(communityTwo.community.title);
|
expect(communityOne.community.title).toBe(communityTwo.community.title);
|
||||||
expect(communityOne.community.description).toBe(
|
expect(communityOne.community.description.unwrapOr("none")).toBe(
|
||||||
communityTwo.community.description
|
communityTwo.community.description.unwrapOr("none")
|
||||||
);
|
);
|
||||||
expect(communityOne.community.icon).toBe(communityTwo.community.icon);
|
expect(communityOne.community.icon.unwrapOr("none")).toBe(communityTwo.community.icon.unwrapOr("none"));
|
||||||
expect(communityOne.community.banner).toBe(communityTwo.community.banner);
|
expect(communityOne.community.banner.unwrapOr("none")).toBe(communityTwo.community.banner.unwrapOr("none"));
|
||||||
expect(communityOne.community.published).toBe(
|
expect(communityOne.community.published).toBe(
|
||||||
communityTwo.community.published
|
communityTwo.community.published
|
||||||
);
|
);
|
||||||
|
@ -47,7 +48,7 @@ test('Create community', async () => {
|
||||||
|
|
||||||
// Cache the community on beta, make sure it has the other fields
|
// Cache the community on beta, make sure it has the other fields
|
||||||
let searchShort = `!${prevName}@lemmy-alpha:8541`;
|
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);
|
assertCommunityFederation(betaCommunity, communityRes.community_view);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ test('Delete community', async () => {
|
||||||
|
|
||||||
// Cache the community on Alpha
|
// Cache the community on Alpha
|
||||||
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
|
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);
|
assertCommunityFederation(alphaCommunity, communityRes.community_view);
|
||||||
|
|
||||||
// Follow the community from alpha
|
// Follow the community from alpha
|
||||||
|
@ -107,7 +108,7 @@ test('Remove community', async () => {
|
||||||
|
|
||||||
// Cache the community on Alpha
|
// Cache the community on Alpha
|
||||||
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
|
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);
|
assertCommunityFederation(alphaCommunity, communityRes.community_view);
|
||||||
|
|
||||||
// Follow the community from alpha
|
// Follow the community from alpha
|
||||||
|
@ -158,6 +159,6 @@ test('Search for beta community', async () => {
|
||||||
expect(communityRes.community_view.community.name).toBeDefined();
|
expect(communityRes.community_view.community.name).toBeDefined();
|
||||||
|
|
||||||
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
|
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);
|
assertCommunityFederation(alphaCommunity, communityRes.community_view);
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,7 +19,7 @@ afterAll(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Follow federated community', async () => {
|
test('Follow federated community', async () => {
|
||||||
let betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
let betaCommunity = (await resolveBetaCommunity(alpha)).community.unwrap();
|
||||||
let follow = await followCommunity(
|
let follow = await followCommunity(
|
||||||
alpha,
|
alpha,
|
||||||
true,
|
true,
|
||||||
|
@ -33,11 +33,11 @@ test('Follow federated community', async () => {
|
||||||
|
|
||||||
// Check it from local
|
// Check it from local
|
||||||
let site = await getSite(alpha);
|
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
|
c => c.community.local == false
|
||||||
).community.id;
|
).community.id;
|
||||||
expect(remoteCommunityId).toBeDefined();
|
expect(remoteCommunityId).toBeDefined();
|
||||||
expect(site.my_user.follows.length).toBe(1);
|
expect(site.my_user.unwrap().follows.length).toBe(1);
|
||||||
|
|
||||||
// Test an unfollow
|
// Test an unfollow
|
||||||
let unfollow = await followCommunity(alpha, false, remoteCommunityId);
|
let unfollow = await followCommunity(alpha, false, remoteCommunityId);
|
||||||
|
@ -45,5 +45,5 @@ test('Follow federated community', async () => {
|
||||||
|
|
||||||
// Make sure you are unsubbed locally
|
// Make sure you are unsubbed locally
|
||||||
let siteUnfollowCheck = await getSite(alpha);
|
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);
|
jest.setTimeout(120000);
|
||||||
|
import {None} from '@sniptt/monads';
|
||||||
|
import { PostView, CommunityView } from 'lemmy-js-client';
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
beta,
|
beta,
|
||||||
|
@ -32,13 +34,12 @@ import {
|
||||||
getSite,
|
getSite,
|
||||||
unfollows
|
unfollows
|
||||||
} from './shared';
|
} from './shared';
|
||||||
import { PostView, CommunityView } from 'lemmy-js-client';
|
|
||||||
|
|
||||||
let betaCommunity: CommunityView;
|
let betaCommunity: CommunityView;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await setupLogins();
|
await setupLogins();
|
||||||
betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
betaCommunity = (await resolveBetaCommunity(alpha)).community.unwrap();
|
||||||
expect(betaCommunity).toBeDefined();
|
expect(betaCommunity).toBeDefined();
|
||||||
await unfollows();
|
await unfollows();
|
||||||
});
|
});
|
||||||
|
@ -50,12 +51,12 @@ afterAll(async () => {
|
||||||
function assertPostFederation(postOne: PostView, postTwo: PostView) {
|
function assertPostFederation(postOne: PostView, postTwo: PostView) {
|
||||||
expect(postOne.post.ap_id).toBe(postTwo.post.ap_id);
|
expect(postOne.post.ap_id).toBe(postTwo.post.ap_id);
|
||||||
expect(postOne.post.name).toBe(postTwo.post.name);
|
expect(postOne.post.name).toBe(postTwo.post.name);
|
||||||
expect(postOne.post.body).toBe(postTwo.post.body);
|
expect(postOne.post.body.unwrapOr("none")).toBe(postTwo.post.body.unwrapOr("none"));
|
||||||
expect(postOne.post.url).toBe(postTwo.post.url);
|
expect(postOne.post.url.unwrapOr("none")).toBe(postTwo.post.url.unwrapOr("none"));
|
||||||
expect(postOne.post.nsfw).toBe(postTwo.post.nsfw);
|
expect(postOne.post.nsfw).toBe(postTwo.post.nsfw);
|
||||||
expect(postOne.post.embed_title).toBe(postTwo.post.embed_title);
|
expect(postOne.post.embed_title.unwrapOr("none")).toBe(postTwo.post.embed_title.unwrapOr("none"));
|
||||||
expect(postOne.post.embed_description).toBe(postTwo.post.embed_description);
|
expect(postOne.post.embed_description.unwrapOr("none")).toBe(postTwo.post.embed_description.unwrapOr("none"));
|
||||||
expect(postOne.post.embed_html).toBe(postTwo.post.embed_html);
|
expect(postOne.post.embed_html.unwrapOr("none")).toBe(postTwo.post.embed_html.unwrapOr("none"));
|
||||||
expect(postOne.post.published).toBe(postTwo.post.published);
|
expect(postOne.post.published).toBe(postTwo.post.published);
|
||||||
expect(postOne.community.actor_id).toBe(postTwo.community.actor_id);
|
expect(postOne.community.actor_id).toBe(postTwo.community.actor_id);
|
||||||
expect(postOne.post.locked).toBe(postTwo.post.locked);
|
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);
|
expect(postRes.post_view.counts.score).toBe(1);
|
||||||
|
|
||||||
// Make sure that post is liked on beta
|
// 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).toBeDefined();
|
||||||
expect(betaPost.community.local).toBe(true);
|
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
|
// Delta only follows beta, so it should not see an alpha ap_id
|
||||||
let deltaPost = (await resolvePost(delta, postRes.post_view.post)).post;
|
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
|
// Epsilon has alpha blocked, it should not see the alpha post
|
||||||
let epsilonPost = (await resolvePost(epsilon, postRes.post_view.post)).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 () => {
|
test('Create a post in a non-existent community', async () => {
|
||||||
let postRes = await createPost(alpha, -2);
|
let postRes = await createPost(alpha, -2) as any;
|
||||||
expect(postRes).toStrictEqual({ error: 'couldnt_find_community' });
|
expect(postRes.error).toBe('couldnt_find_community');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unlike a post', async () => {
|
test('Unlike a post', async () => {
|
||||||
|
@ -103,7 +104,7 @@ test('Unlike a post', async () => {
|
||||||
expect(unlike2.post_view.counts.score).toBe(0);
|
expect(unlike2.post_view.counts.score).toBe(0);
|
||||||
|
|
||||||
// Make sure that post is unliked on beta
|
// 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).toBeDefined();
|
||||||
expect(betaPost.community.local).toBe(true);
|
expect(betaPost.community.local).toBe(true);
|
||||||
expect(betaPost.creator.local).toBe(false);
|
expect(betaPost.creator.local).toBe(false);
|
||||||
|
@ -121,26 +122,26 @@ test('Update a post', async () => {
|
||||||
expect(updatedPost.post_view.creator.local).toBe(true);
|
expect(updatedPost.post_view.creator.local).toBe(true);
|
||||||
|
|
||||||
// Make sure that post is updated on beta
|
// 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.community.local).toBe(true);
|
||||||
expect(betaPost.creator.local).toBe(false);
|
expect(betaPost.creator.local).toBe(false);
|
||||||
expect(betaPost.post.name).toBe(updatedName);
|
expect(betaPost.post.name).toBe(updatedName);
|
||||||
assertPostFederation(betaPost, updatedPost.post_view);
|
assertPostFederation(betaPost, updatedPost.post_view);
|
||||||
|
|
||||||
// Make sure lemmy beta cannot update the post
|
// Make sure lemmy beta cannot update the post
|
||||||
let updatedPostBeta = await editPost(beta, betaPost.post);
|
let updatedPostBeta = await editPost(beta, betaPost.post) as any;
|
||||||
expect(updatedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
|
expect(updatedPostBeta.error).toBe('no_post_edit_allowed');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sticky a post', async () => {
|
test('Sticky a post', async () => {
|
||||||
let postRes = await createPost(alpha, betaCommunity.community.id);
|
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);
|
let stickiedPostRes = await stickyPost(beta, true, betaPost1.post);
|
||||||
expect(stickiedPostRes.post_view.post.stickied).toBe(true);
|
expect(stickiedPostRes.post_view.post.stickied).toBe(true);
|
||||||
|
|
||||||
// Make sure that post is stickied on beta
|
// 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.community.local).toBe(true);
|
||||||
expect(betaPost.creator.local).toBe(false);
|
expect(betaPost.creator.local).toBe(false);
|
||||||
expect(betaPost.post.stickied).toBe(true);
|
expect(betaPost.post.stickied).toBe(true);
|
||||||
|
@ -150,15 +151,15 @@ test('Sticky a post', async () => {
|
||||||
expect(unstickiedPost.post_view.post.stickied).toBe(false);
|
expect(unstickiedPost.post_view.post.stickied).toBe(false);
|
||||||
|
|
||||||
// Make sure that post is unstickied on beta
|
// 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.community.local).toBe(true);
|
||||||
expect(betaPost2.creator.local).toBe(false);
|
expect(betaPost2.creator.local).toBe(false);
|
||||||
expect(betaPost2.post.stickied).toBe(false);
|
expect(betaPost2.post.stickied).toBe(false);
|
||||||
|
|
||||||
// Make sure that gamma cannot sticky the post on beta
|
// 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 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(gammaTrySticky.post_view.post.stickied).toBe(true);
|
||||||
expect(betaPost3.post.stickied).toBe(false);
|
expect(betaPost3.post.stickied).toBe(false);
|
||||||
});
|
});
|
||||||
|
@ -168,7 +169,7 @@ test('Lock a post', async () => {
|
||||||
let postRes = await createPost(alpha, betaCommunity.community.id);
|
let postRes = await createPost(alpha, betaCommunity.community.id);
|
||||||
|
|
||||||
// Lock the post
|
// 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);
|
let lockedPostRes = await lockPost(beta, true, betaPost1.post);
|
||||||
expect(lockedPostRes.post_view.post.locked).toBe(true);
|
expect(lockedPostRes.post_view.post.locked).toBe(true);
|
||||||
|
|
||||||
|
@ -178,7 +179,7 @@ test('Lock a post', async () => {
|
||||||
expect(alphaPost1.post.locked).toBe(true);
|
expect(alphaPost1.post.locked).toBe(true);
|
||||||
|
|
||||||
// Try to make a new comment there, on alpha
|
// 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');
|
expect(comment['error']).toBe('locked');
|
||||||
|
|
||||||
// Unlock a post
|
// Unlock a post
|
||||||
|
@ -193,7 +194,7 @@ test('Lock a post', async () => {
|
||||||
expect(alphaPost2.post.locked).toBe(false);
|
expect(alphaPost2.post.locked).toBe(false);
|
||||||
|
|
||||||
// Try to create a new comment, on alpha
|
// 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();
|
expect(commentAlpha).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -208,32 +209,32 @@ test('Delete a post', async () => {
|
||||||
// Make sure lemmy beta sees post is deleted
|
// Make sure lemmy beta sees post is deleted
|
||||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||||
// This will be undefined because of the tombstone
|
// This will be undefined because of the tombstone
|
||||||
expect(betaPost).toBeUndefined();
|
expect(betaPost.isNone()).toBe(true);
|
||||||
|
|
||||||
// Undelete
|
// Undelete
|
||||||
let undeletedPost = await deletePost(alpha, false, postRes.post_view.post);
|
let undeletedPost = await deletePost(alpha, false, postRes.post_view.post);
|
||||||
expect(undeletedPost.post_view.post.deleted).toBe(false);
|
expect(undeletedPost.post_view.post.deleted).toBe(false);
|
||||||
|
|
||||||
// Make sure lemmy beta sees post is undeleted
|
// 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);
|
expect(betaPost2.post.deleted).toBe(false);
|
||||||
assertPostFederation(betaPost2, undeletedPost.post_view);
|
assertPostFederation(betaPost2, undeletedPost.post_view);
|
||||||
|
|
||||||
// Make sure lemmy beta cannot delete the post
|
// Make sure lemmy beta cannot delete the post
|
||||||
let deletedPostBeta = await deletePost(beta, true, betaPost2.post);
|
let deletedPostBeta = await deletePost(beta, true, betaPost2.post) as any;
|
||||||
expect(deletedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
|
expect(deletedPostBeta.error).toStrictEqual('no_post_edit_allowed');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Remove a post from admin and community on different instance', async () => {
|
test('Remove a post from admin and community on different instance', async () => {
|
||||||
let postRes = await createPost(gamma, betaCommunity.community.id);
|
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);
|
let removedPost = await removePost(alpha, true, alphaPost.post);
|
||||||
expect(removedPost.post_view.post.removed).toBe(true);
|
expect(removedPost.post_view.post.removed).toBe(true);
|
||||||
expect(removedPost.post_view.post.name).toBe(postRes.post_view.post.name);
|
expect(removedPost.post_view.post.name).toBe(postRes.post_view.post.name);
|
||||||
|
|
||||||
// Make sure lemmy beta sees post is NOT removed
|
// 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);
|
expect(betaPost.post.removed).toBe(false);
|
||||||
|
|
||||||
// Undelete
|
// 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);
|
expect(undeletedPost.post_view.post.removed).toBe(false);
|
||||||
|
|
||||||
// Make sure lemmy beta sees post is undeleted
|
// 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);
|
expect(betaPost2.post.removed).toBe(false);
|
||||||
assertPostFederation(betaPost2, undeletedPost.post_view);
|
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);
|
expect(removePostRes.post_view.post.removed).toBe(true);
|
||||||
|
|
||||||
// Make sure lemmy alpha sees post is removed
|
// 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
|
// expect(alphaPost.post_view.post.removed).toBe(true); // TODO this shouldn't be commented
|
||||||
// assertPostFederation(alphaPost.post_view, removePostRes.post_view);
|
// 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);
|
let postRes = await createPost(alpha, betaCommunity.community.id);
|
||||||
expect(postRes.post_view.post).toBeDefined();
|
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();
|
expect(betaPost.post.name).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -294,9 +294,9 @@ test('Enforce site ban for federated user', async () => {
|
||||||
client: alpha.client,
|
client: alpha.client,
|
||||||
auth: alphaUserJwt.jwt,
|
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();
|
expect(alphaUserActorId).toBeDefined();
|
||||||
let alphaPerson = (await resolvePerson(alpha_user, alphaUserActorId)).person;
|
let alphaPerson = (await resolvePerson(alpha_user, alphaUserActorId)).person.unwrap();
|
||||||
expect(alphaPerson).toBeDefined();
|
expect(alphaPerson).toBeDefined();
|
||||||
|
|
||||||
// alpha makes post in beta community, it federates to beta instance
|
// 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
|
// alpha ban should be federated to beta
|
||||||
let alphaUserOnBeta1 = await resolvePerson(beta, alphaUserActorId);
|
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
|
// existing alpha post should be removed on beta
|
||||||
let searchBeta2 = await searchPostLocal(beta, postRes1.post_view.post);
|
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();
|
expect(searchBeta3.posts[0]).toBeDefined();
|
||||||
|
|
||||||
let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId)
|
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 () => {
|
test('Enforce community ban for federated user', async () => {
|
||||||
let alphaShortname = `@lemmy_alpha@lemmy-alpha:8541`;
|
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();
|
expect(alphaPerson).toBeDefined();
|
||||||
|
|
||||||
// make a post in beta, it goes through
|
// 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);
|
let postRes = await createPost(alpha, betaCommunity.community.id);
|
||||||
expect(postRes.post_view.post).toBeDefined();
|
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();
|
expect(betaPost.post.name).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Report a post', async () => {
|
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);
|
let postRes = await createPost(beta, betaCommunity.community.id);
|
||||||
expect(postRes.post_view.post).toBeDefined();
|
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)))
|
let alphaReport = (await reportPost(alpha, alphaPost.post.id, randomString(10)))
|
||||||
.post_report_view.post_report;
|
.post_report_view.post_report;
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ test('Report a post', async () => {
|
||||||
expect(betaReport).toBeDefined();
|
expect(betaReport).toBeDefined();
|
||||||
expect(betaReport.resolved).toBe(false);
|
expect(betaReport.resolved).toBe(false);
|
||||||
expect(betaReport.original_post_name).toBe(alphaReport.original_post_name);
|
expect(betaReport.original_post_name).toBe(alphaReport.original_post_name);
|
||||||
expect(betaReport.original_post_url).toBe(alphaReport.original_post_url);
|
expect(betaReport.original_post_url.unwrapOr("none")).toBe(alphaReport.original_post_url.unwrapOr("none"));
|
||||||
expect(betaReport.original_post_body).toBe(alphaReport.original_post_body);
|
expect(betaReport.original_post_body.unwrapOr("none")).toBe(alphaReport.original_post_body.unwrapOr("none"));
|
||||||
expect(betaReport.reason).toBe(alphaReport.reason);
|
expect(betaReport.reason).toBe(alphaReport.reason);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import {None, Some, Option} from '@sniptt/monads';
|
||||||
import {
|
import {
|
||||||
Login,
|
Login,
|
||||||
LoginResponse,
|
LoginResponse,
|
||||||
|
@ -58,65 +59,74 @@ import {
|
||||||
ListCommentReports,
|
ListCommentReports,
|
||||||
ListCommentReportsResponse,
|
ListCommentReportsResponse,
|
||||||
DeleteAccount,
|
DeleteAccount,
|
||||||
DeleteAccountResponse
|
DeleteAccountResponse,
|
||||||
|
EditSite,
|
||||||
|
CommentSortType,
|
||||||
|
GetComments,
|
||||||
|
GetCommentsResponse
|
||||||
} from 'lemmy-js-client';
|
} from 'lemmy-js-client';
|
||||||
|
|
||||||
export interface API {
|
export interface API {
|
||||||
client: LemmyHttp;
|
client: LemmyHttp;
|
||||||
auth?: string;
|
auth: Option<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let alpha: API = {
|
export let alpha: API = {
|
||||||
client: new LemmyHttp('http://127.0.0.1:8541'),
|
client: new LemmyHttp('http://127.0.0.1:8541'),
|
||||||
|
auth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
export let beta: API = {
|
export let beta: API = {
|
||||||
client: new LemmyHttp('http://127.0.0.1:8551'),
|
client: new LemmyHttp('http://127.0.0.1:8551'),
|
||||||
|
auth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
export let gamma: API = {
|
export let gamma: API = {
|
||||||
client: new LemmyHttp('http://127.0.0.1:8561'),
|
client: new LemmyHttp('http://127.0.0.1:8561'),
|
||||||
|
auth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
export let delta: API = {
|
export let delta: API = {
|
||||||
client: new LemmyHttp('http://127.0.0.1:8571'),
|
client: new LemmyHttp('http://127.0.0.1:8571'),
|
||||||
|
auth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
export let epsilon: API = {
|
export let epsilon: API = {
|
||||||
client: new LemmyHttp('http://127.0.0.1:8581'),
|
client: new LemmyHttp('http://127.0.0.1:8581'),
|
||||||
|
auth: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
const password = 'lemmylemmy'
|
const password = 'lemmylemmy'
|
||||||
|
|
||||||
export async function setupLogins() {
|
export async function setupLogins() {
|
||||||
let formAlpha: Login = {
|
let formAlpha = new Login({
|
||||||
username_or_email: 'lemmy_alpha',
|
username_or_email: 'lemmy_alpha',
|
||||||
password,
|
password,
|
||||||
};
|
});
|
||||||
let resAlpha = alpha.client.login(formAlpha);
|
let resAlpha = alpha.client.login(formAlpha);
|
||||||
|
|
||||||
let formBeta = {
|
let formBeta = new Login({
|
||||||
username_or_email: 'lemmy_beta',
|
username_or_email: 'lemmy_beta',
|
||||||
password,
|
password,
|
||||||
};
|
});
|
||||||
let resBeta = beta.client.login(formBeta);
|
let resBeta = beta.client.login(formBeta);
|
||||||
|
|
||||||
let formGamma = {
|
let formGamma = new Login({
|
||||||
username_or_email: 'lemmy_gamma',
|
username_or_email: 'lemmy_gamma',
|
||||||
password,
|
password,
|
||||||
};
|
});
|
||||||
let resGamma = gamma.client.login(formGamma);
|
let resGamma = gamma.client.login(formGamma);
|
||||||
|
|
||||||
let formDelta = {
|
let formDelta = new Login({
|
||||||
username_or_email: 'lemmy_delta',
|
username_or_email: 'lemmy_delta',
|
||||||
password,
|
password,
|
||||||
};
|
});
|
||||||
let resDelta = delta.client.login(formDelta);
|
let resDelta = delta.client.login(formDelta);
|
||||||
|
|
||||||
let formEpsilon = {
|
let formEpsilon = new Login({
|
||||||
username_or_email: 'lemmy_epsilon',
|
username_or_email: 'lemmy_epsilon',
|
||||||
password,
|
password,
|
||||||
};
|
});
|
||||||
let resEpsilon = epsilon.client.login(formEpsilon);
|
let resEpsilon = epsilon.client.login(formEpsilon);
|
||||||
|
|
||||||
let res = await Promise.all([
|
let res = await Promise.all([
|
||||||
|
@ -133,12 +143,36 @@ export async function setupLogins() {
|
||||||
delta.auth = res[3].jwt;
|
delta.auth = res[3].jwt;
|
||||||
epsilon.auth = res[4].jwt;
|
epsilon.auth = res[4].jwt;
|
||||||
|
|
||||||
// regstration applications are now enabled by default, need to disable them
|
// Registration applications are now enabled by default, need to disable them
|
||||||
await alpha.client.editSite({ require_application: false, auth: alpha.auth});
|
let editSiteForm = new EditSite({
|
||||||
await beta.client.editSite({ require_application: false, auth: beta.auth});
|
name: None,
|
||||||
await gamma.client.editSite({ require_application: false, auth: gamma.auth});
|
sidebar: None,
|
||||||
await delta.client.editSite({ require_application: false, auth: delta.auth});
|
description: None,
|
||||||
await epsilon.client.editSite({ require_application: false, auth: epsilon.auth});
|
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
|
// Create the main beta community, follow it
|
||||||
await createCommunity(beta, "main");
|
await createCommunity(beta, "main");
|
||||||
|
@ -150,27 +184,30 @@ export async function createPost(
|
||||||
community_id: number
|
community_id: number
|
||||||
): Promise<PostResponse> {
|
): Promise<PostResponse> {
|
||||||
let name = randomString(5);
|
let name = randomString(5);
|
||||||
let body = randomString(10);
|
let body = Some(randomString(10));
|
||||||
let url = 'https://google.com/';
|
let url = Some('https://google.com/');
|
||||||
let form: CreatePost = {
|
let form = new CreatePost({
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
body,
|
body,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
community_id,
|
community_id,
|
||||||
nsfw: false,
|
nsfw: None,
|
||||||
};
|
honeypot: None,
|
||||||
|
});
|
||||||
return api.client.createPost(form);
|
return api.client.createPost(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function editPost(api: API, post: Post): Promise<PostResponse> {
|
export async function editPost(api: API, post: Post): Promise<PostResponse> {
|
||||||
let name = 'A jest test federated post, updated';
|
let name = Some('A jest test federated post, updated');
|
||||||
let form: EditPost = {
|
let form = new EditPost({
|
||||||
name,
|
name,
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
nsfw: false,
|
nsfw: None,
|
||||||
};
|
url: None,
|
||||||
|
body: None,
|
||||||
|
});
|
||||||
return api.client.editPost(form);
|
return api.client.editPost(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,11 +216,11 @@ export async function deletePost(
|
||||||
deleted: boolean,
|
deleted: boolean,
|
||||||
post: Post
|
post: Post
|
||||||
): Promise<PostResponse> {
|
): Promise<PostResponse> {
|
||||||
let form: DeletePost = {
|
let form = new DeletePost({
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
deleted: deleted,
|
deleted: deleted,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.deletePost(form);
|
return api.client.deletePost(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,11 +229,12 @@ export async function removePost(
|
||||||
removed: boolean,
|
removed: boolean,
|
||||||
post: Post
|
post: Post
|
||||||
): Promise<PostResponse> {
|
): Promise<PostResponse> {
|
||||||
let form: RemovePost = {
|
let form = new RemovePost({
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
removed,
|
removed,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
reason: None,
|
||||||
|
});
|
||||||
return api.client.removePost(form);
|
return api.client.removePost(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,11 +243,11 @@ export async function stickyPost(
|
||||||
stickied: boolean,
|
stickied: boolean,
|
||||||
post: Post
|
post: Post
|
||||||
): Promise<PostResponse> {
|
): Promise<PostResponse> {
|
||||||
let form: StickyPost = {
|
let form = new StickyPost({
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
stickied,
|
stickied,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.stickyPost(form);
|
return api.client.stickyPost(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,11 +256,11 @@ export async function lockPost(
|
||||||
locked: boolean,
|
locked: boolean,
|
||||||
post: Post
|
post: Post
|
||||||
): Promise<PostResponse> {
|
): Promise<PostResponse> {
|
||||||
let form: LockPost = {
|
let form = new LockPost({
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
locked,
|
locked,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.lockPost(form);
|
return api.client.lockPost(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,9 +268,10 @@ export async function resolvePost(
|
||||||
api: API,
|
api: API,
|
||||||
post: Post
|
post: Post
|
||||||
): Promise<ResolveObjectResponse> {
|
): Promise<ResolveObjectResponse> {
|
||||||
let form: ResolveObject = {
|
let form = new ResolveObject({
|
||||||
q: post.ap_id,
|
q: post.ap_id,
|
||||||
};
|
auth: api.auth,
|
||||||
|
});
|
||||||
return api.client.resolveObject(form);
|
return api.client.resolveObject(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,11 +279,18 @@ export async function searchPostLocal(
|
||||||
api: API,
|
api: API,
|
||||||
post: Post
|
post: Post
|
||||||
): Promise<SearchResponse> {
|
): Promise<SearchResponse> {
|
||||||
let form: Search = {
|
let form = new Search({
|
||||||
q: post.name,
|
q: post.name,
|
||||||
type_: SearchType.Posts,
|
type_: Some(SearchType.Posts),
|
||||||
sort: SortType.TopAll,
|
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);
|
return api.client.search(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,19 +298,42 @@ export async function getPost(
|
||||||
api: API,
|
api: API,
|
||||||
post_id: number
|
post_id: number
|
||||||
): Promise<GetPostResponse> {
|
): Promise<GetPostResponse> {
|
||||||
let form: GetPost = {
|
let form = new GetPost({
|
||||||
id: post_id,
|
id: Some(post_id),
|
||||||
};
|
comment_id: None,
|
||||||
|
auth: api.auth,
|
||||||
|
});
|
||||||
return api.client.getPost(form);
|
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(
|
export async function resolveComment(
|
||||||
api: API,
|
api: API,
|
||||||
comment: Comment
|
comment: Comment
|
||||||
): Promise<ResolveObjectResponse> {
|
): Promise<ResolveObjectResponse> {
|
||||||
let form: ResolveObject = {
|
let form = new ResolveObject({
|
||||||
q: comment.ap_id,
|
q: comment.ap_id,
|
||||||
};
|
auth: api.auth,
|
||||||
|
});
|
||||||
return api.client.resolveObject(form);
|
return api.client.resolveObject(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,9 +341,10 @@ export async function resolveBetaCommunity(
|
||||||
api: API
|
api: API
|
||||||
): Promise<ResolveObjectResponse> {
|
): Promise<ResolveObjectResponse> {
|
||||||
// Use short-hand search url
|
// Use short-hand search url
|
||||||
let form: ResolveObject = {
|
let form = new ResolveObject({
|
||||||
q: '!main@lemmy-beta:8551',
|
q: '!main@lemmy-beta:8551',
|
||||||
};
|
auth: api.auth,
|
||||||
|
});
|
||||||
return api.client.resolveObject(form);
|
return api.client.resolveObject(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,9 +352,10 @@ export async function resolveCommunity(
|
||||||
api: API,
|
api: API,
|
||||||
q: string
|
q: string
|
||||||
): Promise<ResolveObjectResponse> {
|
): Promise<ResolveObjectResponse> {
|
||||||
let form: ResolveObject = {
|
let form = new ResolveObject({
|
||||||
q,
|
q,
|
||||||
};
|
auth: api.auth,
|
||||||
|
});
|
||||||
return api.client.resolveObject(form);
|
return api.client.resolveObject(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,9 +363,10 @@ export async function resolvePerson(
|
||||||
api: API,
|
api: API,
|
||||||
apShortname: string
|
apShortname: string
|
||||||
): Promise<ResolveObjectResponse> {
|
): Promise<ResolveObjectResponse> {
|
||||||
let form: ResolveObject = {
|
let form = new ResolveObject({
|
||||||
q: apShortname,
|
q: apShortname,
|
||||||
};
|
auth: api.auth,
|
||||||
|
});
|
||||||
return api.client.resolveObject(form);
|
return api.client.resolveObject(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,12 +377,14 @@ export async function banPersonFromSite(
|
||||||
remove_data: boolean
|
remove_data: boolean
|
||||||
): Promise<BanPersonResponse> {
|
): Promise<BanPersonResponse> {
|
||||||
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
|
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
|
||||||
let form: BanPerson = {
|
let form = new BanPerson({
|
||||||
person_id,
|
person_id,
|
||||||
ban,
|
ban,
|
||||||
remove_data,
|
remove_data: Some(remove_data),
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
reason: None,
|
||||||
|
expires: None,
|
||||||
|
});
|
||||||
return api.client.banPerson(form);
|
return api.client.banPerson(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,13 +395,15 @@ export async function banPersonFromCommunity(
|
||||||
remove_data: boolean,
|
remove_data: boolean,
|
||||||
ban: boolean
|
ban: boolean
|
||||||
): Promise<BanFromCommunityResponse> {
|
): Promise<BanFromCommunityResponse> {
|
||||||
let form: BanFromCommunity = {
|
let form = new BanFromCommunity({
|
||||||
person_id,
|
person_id,
|
||||||
community_id,
|
community_id,
|
||||||
remove_data,
|
remove_data: Some(remove_data),
|
||||||
ban,
|
ban,
|
||||||
auth: api.auth,
|
reason: None,
|
||||||
};
|
expires: None,
|
||||||
|
auth: api.auth.unwrap(),
|
||||||
|
});
|
||||||
return api.client.banFromCommunity(form);
|
return api.client.banFromCommunity(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,11 +412,11 @@ export async function followCommunity(
|
||||||
follow: boolean,
|
follow: boolean,
|
||||||
community_id: number
|
community_id: number
|
||||||
): Promise<CommunityResponse> {
|
): Promise<CommunityResponse> {
|
||||||
let form: FollowCommunity = {
|
let form = new FollowCommunity({
|
||||||
community_id,
|
community_id,
|
||||||
follow,
|
follow,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap()
|
||||||
};
|
});
|
||||||
return api.client.followCommunity(form);
|
return api.client.followCommunity(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,11 +425,11 @@ export async function likePost(
|
||||||
score: number,
|
score: number,
|
||||||
post: Post
|
post: Post
|
||||||
): Promise<PostResponse> {
|
): Promise<PostResponse> {
|
||||||
let form: CreatePostLike = {
|
let form = new CreatePostLike({
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
score: score,
|
score: score,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap()
|
||||||
};
|
});
|
||||||
|
|
||||||
return api.client.likePost(form);
|
return api.client.likePost(form);
|
||||||
}
|
}
|
||||||
|
@ -361,15 +437,16 @@ export async function likePost(
|
||||||
export async function createComment(
|
export async function createComment(
|
||||||
api: API,
|
api: API,
|
||||||
post_id: number,
|
post_id: number,
|
||||||
parent_id?: number,
|
parent_id: Option<number>,
|
||||||
content = 'a jest test comment'
|
content = 'a jest test comment'
|
||||||
): Promise<CommentResponse> {
|
): Promise<CommentResponse> {
|
||||||
let form: CreateComment = {
|
let form = new CreateComment({
|
||||||
content,
|
content,
|
||||||
post_id,
|
post_id,
|
||||||
parent_id,
|
parent_id,
|
||||||
auth: api.auth,
|
form_id: None,
|
||||||
};
|
auth: api.auth.unwrap(),
|
||||||
|
});
|
||||||
return api.client.createComment(form);
|
return api.client.createComment(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,11 +455,12 @@ export async function editComment(
|
||||||
comment_id: number,
|
comment_id: number,
|
||||||
content = 'A jest test federated comment update'
|
content = 'A jest test federated comment update'
|
||||||
): Promise<CommentResponse> {
|
): Promise<CommentResponse> {
|
||||||
let form: EditComment = {
|
let form = new EditComment({
|
||||||
content,
|
content,
|
||||||
comment_id,
|
comment_id,
|
||||||
auth: api.auth,
|
form_id: None,
|
||||||
};
|
auth: api.auth.unwrap()
|
||||||
|
});
|
||||||
return api.client.editComment(form);
|
return api.client.editComment(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,11 +469,11 @@ export async function deleteComment(
|
||||||
deleted: boolean,
|
deleted: boolean,
|
||||||
comment_id: number
|
comment_id: number
|
||||||
): Promise<CommentResponse> {
|
): Promise<CommentResponse> {
|
||||||
let form: DeleteComment = {
|
let form = new DeleteComment({
|
||||||
comment_id,
|
comment_id,
|
||||||
deleted,
|
deleted,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.deleteComment(form);
|
return api.client.deleteComment(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,20 +482,23 @@ export async function removeComment(
|
||||||
removed: boolean,
|
removed: boolean,
|
||||||
comment_id: number
|
comment_id: number
|
||||||
): Promise<CommentResponse> {
|
): Promise<CommentResponse> {
|
||||||
let form: RemoveComment = {
|
let form = new RemoveComment({
|
||||||
comment_id,
|
comment_id,
|
||||||
removed,
|
removed,
|
||||||
auth: api.auth,
|
reason: None,
|
||||||
};
|
auth: api.auth.unwrap(),
|
||||||
|
});
|
||||||
return api.client.removeComment(form);
|
return api.client.removeComment(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMentions(api: API): Promise<GetPersonMentionsResponse> {
|
export async function getMentions(api: API): Promise<GetPersonMentionsResponse> {
|
||||||
let form: GetPersonMentions = {
|
let form = new GetPersonMentions({
|
||||||
sort: SortType.New,
|
sort: Some(CommentSortType.New),
|
||||||
unread_only: false,
|
unread_only: Some(false),
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
page: None,
|
||||||
|
limit: None,
|
||||||
|
});
|
||||||
return api.client.getPersonMentions(form);
|
return api.client.getPersonMentions(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,11 +507,11 @@ export async function likeComment(
|
||||||
score: number,
|
score: number,
|
||||||
comment: Comment
|
comment: Comment
|
||||||
): Promise<CommentResponse> {
|
): Promise<CommentResponse> {
|
||||||
let form: CreateCommentLike = {
|
let form = new CreateCommentLike({
|
||||||
comment_id: comment.id,
|
comment_id: comment.id,
|
||||||
score,
|
score,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.likeComment(form);
|
return api.client.likeComment(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,14 +519,17 @@ export async function createCommunity(
|
||||||
api: API,
|
api: API,
|
||||||
name_: string = randomString(5)
|
name_: string = randomString(5)
|
||||||
): Promise<CommunityResponse> {
|
): Promise<CommunityResponse> {
|
||||||
let description = 'a sample description';
|
let description = Some('a sample description');
|
||||||
let form: CreateCommunity = {
|
let form = new CreateCommunity({
|
||||||
name: name_,
|
name: name_,
|
||||||
title: name_,
|
title: name_,
|
||||||
description,
|
description,
|
||||||
nsfw: false,
|
nsfw: None,
|
||||||
auth: api.auth,
|
icon: None,
|
||||||
};
|
banner: None,
|
||||||
|
posting_restricted_to_mods: None,
|
||||||
|
auth: api.auth.unwrap(),
|
||||||
|
});
|
||||||
return api.client.createCommunity(form);
|
return api.client.createCommunity(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,9 +537,11 @@ export async function getCommunity(
|
||||||
api: API,
|
api: API,
|
||||||
id: number
|
id: number
|
||||||
): Promise<CommunityResponse> {
|
): Promise<CommunityResponse> {
|
||||||
let form: GetCommunity = {
|
let form = new GetCommunity({
|
||||||
id,
|
id: Some(id),
|
||||||
};
|
name: None,
|
||||||
|
auth: api.auth,
|
||||||
|
});
|
||||||
return api.client.getCommunity(form);
|
return api.client.getCommunity(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,11 +550,11 @@ export async function deleteCommunity(
|
||||||
deleted: boolean,
|
deleted: boolean,
|
||||||
community_id: number
|
community_id: number
|
||||||
): Promise<CommunityResponse> {
|
): Promise<CommunityResponse> {
|
||||||
let form: DeleteCommunity = {
|
let form = new DeleteCommunity({
|
||||||
community_id,
|
community_id,
|
||||||
deleted,
|
deleted,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.deleteCommunity(form);
|
return api.client.deleteCommunity(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,11 +563,13 @@ export async function removeCommunity(
|
||||||
removed: boolean,
|
removed: boolean,
|
||||||
community_id: number
|
community_id: number
|
||||||
): Promise<CommunityResponse> {
|
): Promise<CommunityResponse> {
|
||||||
let form: RemoveCommunity = {
|
let form = new RemoveCommunity({
|
||||||
community_id,
|
community_id,
|
||||||
removed,
|
removed,
|
||||||
auth: api.auth,
|
reason: None,
|
||||||
};
|
expires: None,
|
||||||
|
auth: api.auth.unwrap(),
|
||||||
|
});
|
||||||
return api.client.removeCommunity(form);
|
return api.client.removeCommunity(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,11 +578,11 @@ export async function createPrivateMessage(
|
||||||
recipient_id: number
|
recipient_id: number
|
||||||
): Promise<PrivateMessageResponse> {
|
): Promise<PrivateMessageResponse> {
|
||||||
let content = 'A jest test federated private message';
|
let content = 'A jest test federated private message';
|
||||||
let form: CreatePrivateMessage = {
|
let form = new CreatePrivateMessage({
|
||||||
content,
|
content,
|
||||||
recipient_id,
|
recipient_id,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.createPrivateMessage(form);
|
return api.client.createPrivateMessage(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,11 +591,11 @@ export async function editPrivateMessage(
|
||||||
private_message_id: number
|
private_message_id: number
|
||||||
): Promise<PrivateMessageResponse> {
|
): Promise<PrivateMessageResponse> {
|
||||||
let updatedContent = 'A jest test federated private message edited';
|
let updatedContent = 'A jest test federated private message edited';
|
||||||
let form: EditPrivateMessage = {
|
let form = new EditPrivateMessage({
|
||||||
content: updatedContent,
|
content: updatedContent,
|
||||||
private_message_id,
|
private_message_id,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.editPrivateMessage(form);
|
return api.client.editPrivateMessage(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,11 +604,11 @@ export async function deletePrivateMessage(
|
||||||
deleted: boolean,
|
deleted: boolean,
|
||||||
private_message_id: number
|
private_message_id: number
|
||||||
): Promise<PrivateMessageResponse> {
|
): Promise<PrivateMessageResponse> {
|
||||||
let form: DeletePrivateMessage = {
|
let form = new DeletePrivateMessage({
|
||||||
deleted,
|
deleted,
|
||||||
private_message_id,
|
private_message_id,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.deletePrivateMessage(form);
|
return api.client.deletePrivateMessage(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,32 +616,77 @@ export async function registerUser(
|
||||||
api: API,
|
api: API,
|
||||||
username: string = randomString(5)
|
username: string = randomString(5)
|
||||||
): Promise<LoginResponse> {
|
): Promise<LoginResponse> {
|
||||||
let form: Register = {
|
let form = new Register({
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
password_verify: password,
|
password_verify: password,
|
||||||
show_nsfw: true,
|
show_nsfw: true,
|
||||||
};
|
email: None,
|
||||||
|
captcha_uuid: None,
|
||||||
|
captcha_answer: None,
|
||||||
|
honeypot: None,
|
||||||
|
answer: None,
|
||||||
|
});
|
||||||
return api.client.register(form);
|
return api.client.register(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveUserSettingsBio(
|
export async function saveUserSettingsBio(
|
||||||
api: API
|
api: API
|
||||||
): Promise<LoginResponse> {
|
): Promise<LoginResponse> {
|
||||||
let form: SaveUserSettings = {
|
let form = new SaveUserSettings({
|
||||||
show_nsfw: true,
|
show_nsfw: Some(true),
|
||||||
theme: 'darkly',
|
theme: Some('darkly'),
|
||||||
default_sort_type: Object.keys(SortType).indexOf(SortType.Active),
|
default_sort_type: Some(Object.keys(SortType).indexOf(SortType.Active)),
|
||||||
default_listing_type: Object.keys(ListingType).indexOf(ListingType.All),
|
default_listing_type: Some(Object.keys(ListingType).indexOf(ListingType.All)),
|
||||||
lang: 'en',
|
lang: Some('en'),
|
||||||
show_avatars: true,
|
show_avatars: Some(true),
|
||||||
send_notifications_to_email: false,
|
send_notifications_to_email: Some(false),
|
||||||
bio: 'a changed bio',
|
bio: Some('a changed bio'),
|
||||||
auth: api.auth,
|
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);
|
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(
|
export async function saveUserSettings(
|
||||||
api: API,
|
api: API,
|
||||||
form: SaveUserSettings
|
form: SaveUserSettings
|
||||||
|
@ -564,29 +697,31 @@ export async function saveUserSettings(
|
||||||
export async function deleteUser(
|
export async function deleteUser(
|
||||||
api: API
|
api: API
|
||||||
): Promise<DeleteAccountResponse> {
|
): Promise<DeleteAccountResponse> {
|
||||||
let form: DeleteAccount = {
|
let form = new DeleteAccount({
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
password
|
password
|
||||||
};
|
});
|
||||||
return api.client.deleteAccount(form);
|
return api.client.deleteAccount(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSite(
|
export async function getSite(
|
||||||
api: API
|
api: API
|
||||||
): Promise<GetSiteResponse> {
|
): Promise<GetSiteResponse> {
|
||||||
let form: GetSite = {
|
let form = new GetSite({
|
||||||
auth: api.auth,
|
auth: api.auth,
|
||||||
};
|
});
|
||||||
return api.client.getSite(form);
|
return api.client.getSite(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listPrivateMessages(
|
export async function listPrivateMessages(
|
||||||
api: API
|
api: API
|
||||||
): Promise<PrivateMessagesResponse> {
|
): Promise<PrivateMessagesResponse> {
|
||||||
let form: GetPrivateMessages = {
|
let form = new GetPrivateMessages({
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
unread_only: false,
|
unread_only: Some(false),
|
||||||
};
|
page: None,
|
||||||
|
limit: None,
|
||||||
|
});
|
||||||
return api.client.getPrivateMessages(form);
|
return api.client.getPrivateMessages(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -595,7 +730,7 @@ export async function unfollowRemotes(
|
||||||
): Promise<GetSiteResponse> {
|
): Promise<GetSiteResponse> {
|
||||||
// Unfollow all remote communities
|
// Unfollow all remote communities
|
||||||
let site = await getSite(api);
|
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
|
c => c.community.local == false
|
||||||
);
|
);
|
||||||
for (let cu of remoteFollowed) {
|
for (let cu of remoteFollowed) {
|
||||||
|
@ -607,9 +742,11 @@ export async function unfollowRemotes(
|
||||||
|
|
||||||
export async function followBeta(api: API): Promise<CommunityResponse> {
|
export async function followBeta(api: API): Promise<CommunityResponse> {
|
||||||
let betaCommunity = (await resolveBetaCommunity(api)).community;
|
let betaCommunity = (await resolveBetaCommunity(api)).community;
|
||||||
if (betaCommunity) {
|
if (betaCommunity.isSome()) {
|
||||||
let follow = await followCommunity(api, true, betaCommunity.community.id);
|
let follow = await followCommunity(api, true, betaCommunity.unwrap().community.id);
|
||||||
return follow;
|
return follow;
|
||||||
|
} else {
|
||||||
|
return Promise.reject("no community worked");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,18 +755,22 @@ export async function reportPost(
|
||||||
post_id: number,
|
post_id: number,
|
||||||
reason: string
|
reason: string
|
||||||
): Promise<PostReportResponse> {
|
): Promise<PostReportResponse> {
|
||||||
let form: CreatePostReport = {
|
let form = new CreatePostReport({
|
||||||
post_id,
|
post_id,
|
||||||
reason,
|
reason,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.createPostReport(form);
|
return api.client.createPostReport(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listPostReports(api: API): Promise<ListPostReportsResponse> {
|
export async function listPostReports(api: API): Promise<ListPostReportsResponse> {
|
||||||
let form: ListPostReports = {
|
let form = new ListPostReports({
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
page: None,
|
||||||
|
limit: None,
|
||||||
|
community_id: None,
|
||||||
|
unresolved_only: None,
|
||||||
|
});
|
||||||
return api.client.listPostReports(form);
|
return api.client.listPostReports(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -638,18 +779,22 @@ export async function reportComment(
|
||||||
comment_id: number,
|
comment_id: number,
|
||||||
reason: string
|
reason: string
|
||||||
): Promise<CommentReportResponse> {
|
): Promise<CommentReportResponse> {
|
||||||
let form: CreateCommentReport = {
|
let form = new CreateCommentReport({
|
||||||
comment_id,
|
comment_id,
|
||||||
reason,
|
reason,
|
||||||
auth: api.auth,
|
auth: api.auth.unwrap(),
|
||||||
};
|
});
|
||||||
return api.client.createCommentReport(form);
|
return api.client.createCommentReport(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listCommentReports(api: API): Promise<ListCommentReportsResponse> {
|
export async function listCommentReports(api: API): Promise<ListCommentReportsResponse> {
|
||||||
let form: ListCommentReports = {
|
let form = new ListCommentReports({
|
||||||
auth: api.auth,
|
page: None,
|
||||||
};
|
limit: None,
|
||||||
|
community_id: None,
|
||||||
|
unresolved_only: None,
|
||||||
|
auth: api.auth.unwrap(),
|
||||||
|
});
|
||||||
return api.client.listCommentReports(form);
|
return api.client.listCommentReports(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -681,3 +826,15 @@ export async function unfollows() {
|
||||||
await unfollowRemotes(delta);
|
await unfollowRemotes(delta);
|
||||||
await unfollowRemotes(epsilon);
|
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);
|
jest.setTimeout(120000);
|
||||||
|
import {None} from '@sniptt/monads';
|
||||||
|
import {
|
||||||
|
PersonViewSafe,
|
||||||
|
} from 'lemmy-js-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
beta,
|
beta,
|
||||||
registerUser,
|
registerUser,
|
||||||
resolvePerson,
|
resolvePerson,
|
||||||
saveUserSettings,
|
|
||||||
getSite,
|
getSite,
|
||||||
createPost,
|
createPost,
|
||||||
resolveCommunity,
|
resolveCommunity,
|
||||||
|
@ -14,23 +18,18 @@ import {
|
||||||
resolvePost,
|
resolvePost,
|
||||||
API,
|
API,
|
||||||
resolveComment,
|
resolveComment,
|
||||||
|
saveUserSettingsFederated,
|
||||||
} from './shared';
|
} from './shared';
|
||||||
import {
|
|
||||||
PersonViewSafe,
|
|
||||||
SaveUserSettings,
|
|
||||||
SortType,
|
|
||||||
ListingType,
|
|
||||||
} from 'lemmy-js-client';
|
|
||||||
|
|
||||||
let apShortname: string;
|
let apShortname: string;
|
||||||
|
|
||||||
function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe) {
|
function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe) {
|
||||||
expect(userOne.person.name).toBe(userTwo.person.name);
|
expect(userOne.person.name).toBe(userTwo.person.name);
|
||||||
expect(userOne.person.display_name).toBe(userTwo.person.display_name);
|
expect(userOne.person.display_name.unwrapOr("none")).toBe(userTwo.person.display_name.unwrapOr("none"));
|
||||||
expect(userOne.person.bio).toBe(userTwo.person.bio);
|
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.actor_id).toBe(userTwo.person.actor_id);
|
||||||
expect(userOne.person.avatar).toBe(userTwo.person.avatar);
|
expect(userOne.person.avatar.unwrapOr("none")).toBe(userTwo.person.avatar.unwrapOr("none"));
|
||||||
expect(userOne.person.banner).toBe(userTwo.person.banner);
|
expect(userOne.person.banner.unwrapOr("none")).toBe(userTwo.person.banner.unwrapOr("none"));
|
||||||
expect(userOne.person.published).toBe(userTwo.person.published);
|
expect(userOne.person.published).toBe(userTwo.person.published);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,31 +40,13 @@ test('Create user', async () => {
|
||||||
|
|
||||||
let site = await getSite(alpha);
|
let site = await getSite(alpha);
|
||||||
expect(site.my_user).toBeDefined();
|
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 () => {
|
test('Set some user settings, check that they are federated', async () => {
|
||||||
let avatar = 'https://image.flaticon.com/icons/png/512/35/35896.png';
|
await saveUserSettingsFederated(alpha);
|
||||||
let banner = 'https://image.flaticon.com/icons/png/512/36/35896.png';
|
let alphaPerson = (await resolvePerson(alpha, apShortname)).person.unwrap();
|
||||||
let bio = 'a changed bio';
|
let betaPerson = (await resolvePerson(beta, apShortname)).person.unwrap();
|
||||||
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;
|
|
||||||
assertUserFederation(alphaPerson, betaPerson);
|
assertUserFederation(alphaPerson, betaPerson);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -78,23 +59,23 @@ test('Delete user', async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a local post and comment
|
// 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;
|
let localPost = (await createPost(user, alphaCommunity.community.id)).post_view.post;
|
||||||
expect(localPost).toBeDefined();
|
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();
|
expect(localComment).toBeDefined();
|
||||||
|
|
||||||
// make a remote post and comment
|
// 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;
|
let remotePost = (await createPost(user, betaCommunity.community.id)).post_view.post;
|
||||||
expect(remotePost).toBeDefined();
|
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();
|
expect(remoteComment).toBeDefined();
|
||||||
|
|
||||||
await deleteUser(user);
|
await deleteUser(user);
|
||||||
|
|
||||||
expect((await resolvePost(alpha, localPost)).post).toBeUndefined();
|
expect((await resolvePost(alpha, localPost)).post.isNone()).toBe(true);
|
||||||
expect((await resolveComment(alpha, localComment)).comment).toBeUndefined();
|
expect((await resolveComment(alpha, localComment)).comment.isNone()).toBe(true)
|
||||||
expect((await resolvePost(alpha, remotePost)).post).toBeUndefined();
|
expect((await resolvePost(alpha, remotePost)).post.isNone()).toBe(true)
|
||||||
expect((await resolveComment(alpha, remoteComment)).comment).toBeUndefined();
|
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::{
|
use lemmy_db_schema::{
|
||||||
newtypes::LocalUserId,
|
newtypes::LocalUserId,
|
||||||
source::comment::{CommentLike, CommentLikeForm},
|
source::{
|
||||||
|
comment::{CommentLike, CommentLikeForm},
|
||||||
|
comment_reply::CommentReply,
|
||||||
|
},
|
||||||
traits::Likeable,
|
traits::Likeable,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||||
|
@ -53,14 +56,20 @@ impl Perform for CreateCommentLike {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Add parent user to recipients
|
// Add parent poster or commenter to recipients
|
||||||
let recipient_id = orig_comment.get_recipient_id();
|
let comment_reply = blocking(context.pool(), move |conn| {
|
||||||
if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
|
CommentReply::read_by_comment(conn, comment_id)
|
||||||
LocalUserView::read_person(conn, recipient_id)
|
|
||||||
})
|
})
|
||||||
.await?
|
.await?;
|
||||||
{
|
if let Ok(reply) = comment_reply {
|
||||||
recipient_ids.push(local_recipient.local_user.id);
|
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 {
|
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 like;
|
||||||
mod mark_as_read;
|
|
||||||
mod save;
|
mod save;
|
||||||
|
|
|
@ -60,6 +60,9 @@ pub async fn match_websocket_operation(
|
||||||
UserOperation::MarkPersonMentionAsRead => {
|
UserOperation::MarkPersonMentionAsRead => {
|
||||||
do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
|
do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
|
UserOperation::MarkCommentReplyAsRead => {
|
||||||
|
do_websocket_operation::<MarkCommentReplyAsRead>(context, id, op, data).await
|
||||||
|
}
|
||||||
UserOperation::MarkAllAsRead => {
|
UserOperation::MarkAllAsRead => {
|
||||||
do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
|
do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
|
@ -155,9 +158,6 @@ pub async fn match_websocket_operation(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment ops
|
// Comment ops
|
||||||
UserOperation::MarkCommentAsRead => {
|
|
||||||
do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
|
|
||||||
}
|
|
||||||
UserOperation::SaveComment => {
|
UserOperation::SaveComment => {
|
||||||
do_websocket_operation::<SaveComment>(context, id, op, data).await
|
do_websocket_operation::<SaveComment>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,15 @@ impl Perform for GetPersonMentions {
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
let unread_only = data.unread_only;
|
let unread_only = data.unread_only;
|
||||||
let person_id = local_user_view.person.id;
|
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| {
|
let mentions = blocking(context.pool(), move |conn| {
|
||||||
PersonMentionQueryBuilder::create(conn)
|
PersonMentionQueryBuilder::create(conn)
|
||||||
.recipient_id(person_id)
|
.recipient_id(person_id)
|
||||||
.my_person_id(person_id)
|
.my_person_id(person_id)
|
||||||
.sort(sort)
|
.sort(sort)
|
||||||
.unread_only(unread_only)
|
.unread_only(unread_only)
|
||||||
|
.show_bot_accounts(show_bot_accounts)
|
||||||
.page(page)
|
.page(page)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.list()
|
.list()
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
person::{GetReplies, GetRepliesResponse},
|
person::{GetReplies, GetRepliesResponse},
|
||||||
utils::{blocking, get_local_user_view_from_jwt},
|
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_utils::{error::LemmyError, ConnectionId};
|
||||||
use lemmy_websocket::LemmyContext;
|
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 show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||||
|
|
||||||
let replies = blocking(context.pool(), move |conn| {
|
let replies = blocking(context.pool(), move |conn| {
|
||||||
CommentQueryBuilder::create(conn)
|
CommentReplyQueryBuilder::create(conn)
|
||||||
|
.recipient_id(person_id)
|
||||||
|
.my_person_id(person_id)
|
||||||
.sort(sort)
|
.sort(sort)
|
||||||
.unread_only(unread_only)
|
.unread_only(unread_only)
|
||||||
.recipient_id(person_id)
|
|
||||||
.show_bot_accounts(show_bot_accounts)
|
.show_bot_accounts(show_bot_accounts)
|
||||||
.my_person_id(person_id)
|
|
||||||
.page(page)
|
.page(page)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.list()
|
.list()
|
||||||
|
|
|
@ -5,11 +5,10 @@ use lemmy_api_common::{
|
||||||
utils::{blocking, get_local_user_view_from_jwt},
|
utils::{blocking, get_local_user_view_from_jwt},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
comment::Comment,
|
comment_reply::CommentReply,
|
||||||
person_mention::PersonMention,
|
person_mention::PersonMention,
|
||||||
private_message::PrivateMessage,
|
private_message::PrivateMessage,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::comment_view::CommentQueryBuilder;
|
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
|
@ -26,42 +25,28 @@ impl Perform for MarkAllAsRead {
|
||||||
let data: &MarkAllAsRead = self;
|
let data: &MarkAllAsRead = self;
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
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
|
// Mark all comment_replies as read
|
||||||
// Not easy to do as a bulk operation,
|
blocking(context.pool(), move |conn| {
|
||||||
// because recipient_id isn't in the comment table
|
CommentReply::mark_all_as_read(conn, person_id)
|
||||||
for comment_view in &replies {
|
})
|
||||||
let reply_id = comment_view.comment.id;
|
.await?
|
||||||
let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||||
blocking(context.pool(), mark_as_read)
|
|
||||||
.await?
|
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark all user mentions as read
|
// Mark all user mentions as read
|
||||||
let update_person_mentions =
|
blocking(context.pool(), move |conn| {
|
||||||
move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
|
PersonMention::mark_all_as_read(conn, person_id)
|
||||||
blocking(context.pool(), update_person_mentions)
|
})
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
|
||||||
|
|
||||||
// Mark all private_messages as read
|
// Mark all private_messages as read
|
||||||
let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
|
blocking(context.pool(), move |conn| {
|
||||||
blocking(context.pool(), update_pm)
|
PrivateMessage::mark_all_as_read(conn, person_id)
|
||||||
.await?
|
})
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
.await?
|
||||||
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;
|
||||||
|
|
||||||
Ok(GetRepliesResponse { replies: vec![] })
|
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 list_replies;
|
||||||
mod mark_all_read;
|
mod mark_all_read;
|
||||||
mod mark_mention_read;
|
mod mark_mention_read;
|
||||||
|
mod mark_reply_read;
|
||||||
mod unread_count;
|
mod unread_count;
|
||||||
|
|
|
@ -4,8 +4,8 @@ use lemmy_api_common::{
|
||||||
person::{GetUnreadCount, GetUnreadCountResponse},
|
person::{GetUnreadCount, GetUnreadCountResponse},
|
||||||
utils::{blocking, get_local_user_view_from_jwt},
|
utils::{blocking, get_local_user_view_from_jwt},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, PrivateMessageView};
|
use lemmy_db_views::structs::PrivateMessageView;
|
||||||
use lemmy_db_views_actor::structs::PersonMentionView;
|
use lemmy_db_views_actor::structs::{CommentReplyView, PersonMentionView};
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ impl Perform for GetUnreadCount {
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
|
|
||||||
let replies = blocking(context.pool(), move |conn| {
|
let replies = blocking(context.pool(), move |conn| {
|
||||||
CommentView::get_unread_replies(conn, person_id)
|
CommentReplyView::get_unread_replies(conn, person_id)
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,12 @@ use lemmy_api_common::{
|
||||||
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
|
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
|
||||||
};
|
};
|
||||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
|
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::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
community_view::CommunityQueryBuilder,
|
community_view::CommunityQueryBuilder,
|
||||||
|
@ -88,7 +93,7 @@ impl Perform for Search {
|
||||||
SearchType::Comments => {
|
SearchType::Comments => {
|
||||||
comments = blocking(context.pool(), move |conn| {
|
comments = blocking(context.pool(), move |conn| {
|
||||||
CommentQueryBuilder::create(conn)
|
CommentQueryBuilder::create(conn)
|
||||||
.sort(sort)
|
.sort(sort.map(post_to_comment_sort_type))
|
||||||
.listing_type(listing_type)
|
.listing_type(listing_type)
|
||||||
.search_term(q)
|
.search_term(q)
|
||||||
.show_bot_accounts(show_bot_accounts)
|
.show_bot_accounts(show_bot_accounts)
|
||||||
|
@ -155,7 +160,7 @@ impl Perform for Search {
|
||||||
|
|
||||||
comments = blocking(context.pool(), move |conn| {
|
comments = blocking(context.pool(), move |conn| {
|
||||||
CommentQueryBuilder::create(conn)
|
CommentQueryBuilder::create(conn)
|
||||||
.sort(sort)
|
.sort(sort.map(post_to_comment_sort_type))
|
||||||
.listing_type(listing_type)
|
.listing_type(listing_type)
|
||||||
.search_term(q)
|
.search_term(q)
|
||||||
.show_bot_accounts(show_bot_accounts)
|
.show_bot_accounts(show_bot_accounts)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::sensitive::Sensitive;
|
use crate::sensitive::Sensitive;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommentReportId, CommunityId, LocalUserId, PostId},
|
newtypes::{CommentId, CommentReportId, CommunityId, LocalUserId, PostId},
|
||||||
|
CommentSortType,
|
||||||
ListingType,
|
ListingType,
|
||||||
SortType,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentReportView, CommentView};
|
use lemmy_db_views::structs::{CommentReportView, CommentView};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -45,13 +45,6 @@ pub struct RemoveComment {
|
||||||
pub auth: Sensitive<String>,
|
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)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct SaveComment {
|
pub struct SaveComment {
|
||||||
pub comment_id: CommentId,
|
pub comment_id: CommentId,
|
||||||
|
@ -76,11 +69,14 @@ pub struct CreateCommentLike {
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct GetComments {
|
pub struct GetComments {
|
||||||
pub type_: Option<ListingType>,
|
pub type_: Option<ListingType>,
|
||||||
pub sort: Option<SortType>,
|
pub sort: Option<CommentSortType>,
|
||||||
|
pub max_depth: Option<i32>,
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
pub community_name: Option<String>,
|
pub community_name: Option<String>,
|
||||||
|
pub post_id: Option<PostId>,
|
||||||
|
pub parent_id: Option<CommentId>,
|
||||||
pub saved_only: Option<bool>,
|
pub saved_only: Option<bool>,
|
||||||
pub auth: Option<Sensitive<String>>,
|
pub auth: Option<Sensitive<String>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use crate::sensitive::Sensitive;
|
use crate::sensitive::Sensitive;
|
||||||
use lemmy_db_views::structs::{CommentView, PostView, PrivateMessageView};
|
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};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
@ -9,7 +14,8 @@ pub struct Login {
|
||||||
pub password: Sensitive<String>,
|
pub password: Sensitive<String>,
|
||||||
}
|
}
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommunityId, PersonId, PersonMentionId, PrivateMessageId},
|
newtypes::{CommentReplyId, CommunityId, PersonId, PersonMentionId, PrivateMessageId},
|
||||||
|
CommentSortType,
|
||||||
SortType,
|
SortType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,7 +111,7 @@ pub struct GetPersonDetailsResponse {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct GetRepliesResponse {
|
pub struct GetRepliesResponse {
|
||||||
pub replies: Vec<CommentView>,
|
pub replies: Vec<CommentReplyView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
@ -171,7 +177,7 @@ pub struct BlockPersonResponse {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct GetReplies {
|
pub struct GetReplies {
|
||||||
pub sort: Option<SortType>,
|
pub sort: Option<CommentSortType>,
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
pub unread_only: Option<bool>,
|
pub unread_only: Option<bool>,
|
||||||
|
@ -180,7 +186,7 @@ pub struct GetReplies {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct GetPersonMentions {
|
pub struct GetPersonMentions {
|
||||||
pub sort: Option<SortType>,
|
pub sort: Option<CommentSortType>,
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
pub unread_only: Option<bool>,
|
pub unread_only: Option<bool>,
|
||||||
|
@ -199,6 +205,18 @@ pub struct PersonMentionResponse {
|
||||||
pub person_mention_view: PersonMentionView,
|
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)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct DeleteAccount {
|
pub struct DeleteAccount {
|
||||||
pub password: Sensitive<String>,
|
pub password: Sensitive<String>,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::sensitive::Sensitive;
|
use crate::sensitive::Sensitive;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommunityId, DbUrl, PostId, PostReportId},
|
newtypes::{CommentId, CommunityId, DbUrl, PostId, PostReportId},
|
||||||
ListingType,
|
ListingType,
|
||||||
SortType,
|
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 lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -27,7 +27,8 @@ pub struct PostResponse {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct GetPost {
|
pub struct GetPost {
|
||||||
pub id: PostId,
|
pub id: Option<PostId>,
|
||||||
|
pub comment_id: Option<CommentId>,
|
||||||
pub auth: Option<Sensitive<String>>,
|
pub auth: Option<Sensitive<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +36,6 @@ pub struct GetPost {
|
||||||
pub struct GetPostResponse {
|
pub struct GetPostResponse {
|
||||||
pub post_view: PostView,
|
pub post_view: PostView,
|
||||||
pub community_view: CommunityView,
|
pub community_view: CommunityView,
|
||||||
pub comments: Vec<CommentView>,
|
|
||||||
pub moderators: Vec<CommunityModeratorView>,
|
pub moderators: Vec<CommunityModeratorView>,
|
||||||
pub online: usize,
|
pub online: usize,
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,11 @@ use lemmy_apub::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
|
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
|
||||||
|
comment_reply::CommentReply,
|
||||||
person_mention::PersonMention,
|
person_mention::PersonMention,
|
||||||
},
|
},
|
||||||
traits::{Crud, Likeable},
|
traits::{Crud, Likeable},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::CommentView;
|
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::LemmyError,
|
error::LemmyError,
|
||||||
utils::{remove_slurs, scrape_text_for_mentions},
|
utils::{remove_slurs, scrape_text_for_mentions},
|
||||||
|
@ -67,14 +67,18 @@ impl PerformCrud for CreateComment {
|
||||||
return Err(LemmyError::from_message("locked"));
|
return Err(LemmyError::from_message("locked"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's a parent_id, check to make sure that comment is in that post
|
// Fetch the parent, if it exists
|
||||||
if let Some(parent_id) = data.parent_id {
|
let parent_opt = if let Some(parent_id) = data.parent_id {
|
||||||
// Make sure the parent comment exists
|
blocking(context.pool(), move |conn| Comment::read(conn, parent_id))
|
||||||
let parent = blocking(context.pool(), move |conn| Comment::read(conn, parent_id))
|
|
||||||
.await?
|
.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 {
|
if parent.post_id != post_id {
|
||||||
return Err(LemmyError::from_message("couldnt_create_comment"));
|
return Err(LemmyError::from_message("couldnt_create_comment"));
|
||||||
}
|
}
|
||||||
|
@ -82,7 +86,6 @@ impl PerformCrud for CreateComment {
|
||||||
|
|
||||||
let comment_form = CommentForm {
|
let comment_form = CommentForm {
|
||||||
content: content_slurs_removed,
|
content: content_slurs_removed,
|
||||||
parent_id: data.parent_id.to_owned(),
|
|
||||||
post_id: data.post_id,
|
post_id: data.post_id,
|
||||||
creator_id: local_user_view.person.id,
|
creator_id: local_user_view.person.id,
|
||||||
..CommentForm::default()
|
..CommentForm::default()
|
||||||
|
@ -90,8 +93,9 @@ impl PerformCrud for CreateComment {
|
||||||
|
|
||||||
// Create the comment
|
// Create the comment
|
||||||
let comment_form2 = comment_form.clone();
|
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| {
|
let inserted_comment = blocking(context.pool(), move |conn| {
|
||||||
Comment::create(conn, &comment_form2)
|
Comment::create(conn, &comment_form2, parent_path.as_ref())
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_comment"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_comment"))?;
|
||||||
|
@ -148,35 +152,21 @@ impl PerformCrud for CreateComment {
|
||||||
)
|
)
|
||||||
.await?;
|
.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 its a reply, mark the parent as read
|
||||||
if let Some(parent_id) = data.parent_id {
|
if let Some(parent) = parent_opt {
|
||||||
let parent_comment = blocking(context.pool(), move |conn| {
|
let parent_id = parent.id;
|
||||||
CommentView::read(conn, parent_id, Some(person_id))
|
let comment_reply = blocking(context.pool(), move |conn| {
|
||||||
|
CommentReply::read_by_comment(conn, parent_id)
|
||||||
})
|
})
|
||||||
.await??;
|
.await?;
|
||||||
if local_user_view.person.id == parent_comment.get_recipient_id() {
|
if let Ok(reply) = comment_reply {
|
||||||
blocking(context.pool(), move |conn| {
|
blocking(context.pool(), move |conn| {
|
||||||
Comment::update_read(conn, parent_id, true)
|
CommentReply::update_read(conn, reply.id, true)
|
||||||
})
|
})
|
||||||
.await?
|
.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
|
// If the parent has PersonMentions mark them as read too
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let person_mention = blocking(context.pool(), move |conn| {
|
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_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_db_views::comment_view::CommentQueryBuilder;
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
@ -49,16 +52,34 @@ impl PerformCrud for GetComments {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let sort = data.sort;
|
let sort = data.sort;
|
||||||
|
let max_depth = data.max_depth;
|
||||||
let saved_only = data.saved_only;
|
let saved_only = data.saved_only;
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
let limit = data.limit;
|
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| {
|
let mut comments = blocking(context.pool(), move |conn| {
|
||||||
CommentQueryBuilder::create(conn)
|
CommentQueryBuilder::create(conn)
|
||||||
.listing_type(listing_type)
|
.listing_type(listing_type)
|
||||||
.sort(sort)
|
.sort(sort)
|
||||||
|
.max_depth(max_depth)
|
||||||
.saved_only(saved_only)
|
.saved_only(saved_only)
|
||||||
.community_id(community_id)
|
.community_id(community_id)
|
||||||
.community_actor_id(community_actor_id)
|
.community_actor_id(community_actor_id)
|
||||||
|
.parent_path(parent_path)
|
||||||
|
.post_id(post_id)
|
||||||
.my_person_id(person_id)
|
.my_person_id(person_id)
|
||||||
.show_bot_accounts(show_bot_accounts)
|
.show_bot_accounts(show_bot_accounts)
|
||||||
.page(page)
|
.page(page)
|
||||||
|
|
|
@ -4,8 +4,11 @@ use lemmy_api_common::{
|
||||||
post::{GetPost, GetPostResponse},
|
post::{GetPost, GetPostResponse},
|
||||||
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt, mark_post_as_read},
|
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_schema::{
|
||||||
use lemmy_db_views::{comment_view::CommentQueryBuilder, structs::PostView};
|
source::comment::Comment,
|
||||||
|
traits::{Crud, DeleteableOrRemoveable},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::PostView;
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
use lemmy_websocket::{messages::GetPostUsersOnline, LemmyContext};
|
use lemmy_websocket::{messages::GetPostUsersOnline, LemmyContext};
|
||||||
|
@ -27,35 +30,33 @@ impl PerformCrud for GetPost {
|
||||||
|
|
||||||
check_private_instance(&local_user_view, context.pool()).await?;
|
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 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| {
|
let mut post_view = blocking(context.pool(), move |conn| {
|
||||||
PostView::read(conn, id, person_id)
|
PostView::read(conn, post_id, person_id)
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?;
|
||||||
|
|
||||||
// Mark the post as read
|
// Mark the post as read
|
||||||
|
let post_id = post_view.post.id;
|
||||||
if let Some(person_id) = person_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;
|
// Necessary for the sidebar subscribed
|
||||||
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
|
|
||||||
let community_id = post_view.community.id;
|
let community_id = post_view.community.id;
|
||||||
let mut community_view = blocking(context.pool(), move |conn| {
|
let mut community_view = blocking(context.pool(), move |conn| {
|
||||||
CommunityView::read(conn, community_id, person_id)
|
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();
|
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 {
|
if community_view.community.deleted || community_view.community.removed {
|
||||||
community_view.community = community_view.community.blank_out_deleted_or_removed_info();
|
community_view.community = community_view.community.blank_out_deleted_or_removed_info();
|
||||||
}
|
}
|
||||||
|
@ -87,7 +82,7 @@ impl PerformCrud for GetPost {
|
||||||
|
|
||||||
let online = context
|
let online = context
|
||||||
.chat_server()
|
.chat_server()
|
||||||
.send(GetPostUsersOnline { post_id: data.id })
|
.send(GetPostUsersOnline { post_id })
|
||||||
.await
|
.await
|
||||||
.unwrap_or(1);
|
.unwrap_or(1);
|
||||||
|
|
||||||
|
@ -95,7 +90,6 @@ impl PerformCrud for GetPost {
|
||||||
Ok(GetPostResponse {
|
Ok(GetPostResponse {
|
||||||
post_view,
|
post_view,
|
||||||
community_view,
|
community_view,
|
||||||
comments,
|
|
||||||
moderators,
|
moderators,
|
||||||
online,
|
online,
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
|
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
|
||||||
};
|
};
|
||||||
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::person::ApubPerson};
|
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::{comment_view::CommentQueryBuilder, post_view::PostQueryBuilder};
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonViewSafe};
|
use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonViewSafe};
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
|
@ -88,7 +88,7 @@ impl PerformCrud for GetPersonDetails {
|
||||||
let mut comments_query = CommentQueryBuilder::create(conn)
|
let mut comments_query = CommentQueryBuilder::create(conn)
|
||||||
.my_person_id(person_id)
|
.my_person_id(person_id)
|
||||||
.show_bot_accounts(show_bot_accounts)
|
.show_bot_accounts(show_bot_accounts)
|
||||||
.sort(sort)
|
.sort(sort.map(post_to_comment_sort_type))
|
||||||
.saved_only(saved_only)
|
.saved_only(saved_only)
|
||||||
.community_id(community_id)
|
.community_id(community_id)
|
||||||
.page(page)
|
.page(page)
|
||||||
|
|
|
@ -104,7 +104,7 @@ async fn get_comment_parent_creator(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
comment: &Comment,
|
comment: &Comment,
|
||||||
) -> Result<ApubPerson, LemmyError> {
|
) -> 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 =
|
let parent_comment =
|
||||||
blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
|
blocking(pool, move |conn| Comment::read(conn, parent_comment_id)).await??;
|
||||||
parent_comment.creator_id
|
parent_comment.creator_id
|
||||||
|
|
|
@ -98,7 +98,7 @@ impl ApubObject for ApubComment {
|
||||||
})
|
})
|
||||||
.await??;
|
.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 =
|
let parent_comment =
|
||||||
blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
|
blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
|
||||||
ObjectId::<PostOrComment>::new(parent_comment.ap_id)
|
ObjectId::<PostOrComment>::new(parent_comment.ap_id)
|
||||||
|
@ -170,7 +170,7 @@ impl ApubObject for ApubComment {
|
||||||
.attributed_to
|
.attributed_to
|
||||||
.dereference(context, local_instance(context), request_counter)
|
.dereference(context, local_instance(context), request_counter)
|
||||||
.await?;
|
.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 = read_from_string_or_source(¬e.content, ¬e.media_type, ¬e.source);
|
||||||
let content_slurs_removed = remove_slurs(&content, &context.settings().slur_regex());
|
let content_slurs_removed = remove_slurs(&content, &context.settings().slur_regex());
|
||||||
|
@ -178,17 +178,19 @@ impl ApubObject for ApubComment {
|
||||||
let form = CommentForm {
|
let form = CommentForm {
|
||||||
creator_id: creator.id,
|
creator_id: creator.id,
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
parent_id: parent_comment_id,
|
|
||||||
content: content_slurs_removed,
|
content: content_slurs_removed,
|
||||||
removed: None,
|
removed: None,
|
||||||
read: None,
|
|
||||||
published: note.published.map(|u| u.naive_local()),
|
published: note.published.map(|u| u.naive_local()),
|
||||||
updated: note.updated.map(|u| u.naive_local()),
|
updated: note.updated.map(|u| u.naive_local()),
|
||||||
deleted: None,
|
deleted: None,
|
||||||
ap_id: Some(note.id.into()),
|
ap_id: Some(note.id.into()),
|
||||||
local: Some(false),
|
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())
|
Ok(comment.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ use activitypub_federation::{
|
||||||
use activitystreams_kinds::object::NoteType;
|
use activitystreams_kinds::object::NoteType;
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use lemmy_api_common::utils::blocking;
|
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_utils::error::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -51,7 +51,7 @@ impl Note {
|
||||||
&self,
|
&self,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
request_counter: &mut i32,
|
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.
|
// Fetch parent comment chain in a box, otherwise it can cause a stack overflow.
|
||||||
let parent = Box::pin(
|
let parent = Box::pin(
|
||||||
self
|
self
|
||||||
|
@ -61,16 +61,14 @@ impl Note {
|
||||||
);
|
);
|
||||||
match parent.deref() {
|
match parent.deref() {
|
||||||
PostOrComment::Post(p) => {
|
PostOrComment::Post(p) => {
|
||||||
// Workaround because I cant figure out how to get the post out of the box (and we dont
|
let post = p.deref().to_owned();
|
||||||
// want to stackoverflow in a deep comment hierarchy).
|
Ok((post, None))
|
||||||
let post_id = p.id;
|
|
||||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
|
||||||
Ok((post.into(), None))
|
|
||||||
}
|
}
|
||||||
PostOrComment::Comment(c) => {
|
PostOrComment::Comment(c) => {
|
||||||
let post_id = c.post_id;
|
let post_id = c.post_id;
|
||||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
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 }
|
sha2 = { version = "0.10.2", optional = true }
|
||||||
regex = { version = "1.5.5", optional = true }
|
regex = { version = "1.5.5", optional = true }
|
||||||
once_cell = { version = "1.10.0", optional = true }
|
once_cell = { version = "1.10.0", optional = true }
|
||||||
|
diesel_ltree = "0.2.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = "0.6.0"
|
serial_test = "0.6.0"
|
||||||
|
|
|
@ -74,17 +74,17 @@ mod tests {
|
||||||
..CommentForm::default()
|
..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 {
|
let child_comment_form = CommentForm {
|
||||||
content: "A test comment".into(),
|
content: "A test comment".into(),
|
||||||
creator_id: inserted_person.id,
|
creator_id: inserted_person.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: Some(inserted_comment.id),
|
|
||||||
..CommentForm::default()
|
..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 {
|
let comment_like = CommentLikeForm {
|
||||||
comment_id: inserted_comment.id,
|
comment_id: inserted_comment.id,
|
||||||
|
|
|
@ -107,17 +107,17 @@ mod tests {
|
||||||
..CommentForm::default()
|
..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 {
|
let child_comment_form = CommentForm {
|
||||||
content: "A test comment".into(),
|
content: "A test comment".into(),
|
||||||
creator_id: inserted_person.id,
|
creator_id: inserted_person.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: Some(inserted_comment.id),
|
|
||||||
..CommentForm::default()
|
..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 =
|
let community_aggregates_before_delete =
|
||||||
CommunityAggregates::read(&conn, inserted_community.id).unwrap();
|
CommunityAggregates::read(&conn, inserted_community.id).unwrap();
|
||||||
|
|
|
@ -78,7 +78,7 @@ mod tests {
|
||||||
..CommentForm::default()
|
..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 {
|
let mut comment_like = CommentLikeForm {
|
||||||
comment_id: inserted_comment.id,
|
comment_id: inserted_comment.id,
|
||||||
|
@ -89,15 +89,15 @@ mod tests {
|
||||||
|
|
||||||
let _inserted_comment_like = CommentLike::like(&conn, &comment_like).unwrap();
|
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(),
|
content: "A test comment".into(),
|
||||||
creator_id: inserted_person.id,
|
creator_id: inserted_person.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: Some(inserted_comment.id),
|
|
||||||
..CommentForm::default()
|
..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 {
|
let child_comment_like = CommentLikeForm {
|
||||||
comment_id: inserted_child_comment.id,
|
comment_id: inserted_child_comment.id,
|
||||||
|
@ -123,14 +123,15 @@ mod tests {
|
||||||
|
|
||||||
// Remove a parent comment (the scores should also be removed)
|
// Remove a parent comment (the scores should also be removed)
|
||||||
Comment::delete(&conn, inserted_comment.id).unwrap();
|
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();
|
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_count);
|
||||||
assert_eq!(0, after_parent_comment_delete.comment_score);
|
assert_eq!(0, after_parent_comment_delete.comment_score);
|
||||||
|
|
||||||
// Add in the two comments again, then delete the post.
|
// Add in the two comments again, then delete the post.
|
||||||
let new_parent_comment = Comment::create(&conn, &comment_form).unwrap();
|
let new_parent_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||||
child_comment_form.parent_id = Some(new_parent_comment.id);
|
let _new_child_comment =
|
||||||
Comment::create(&conn, &child_comment_form).unwrap();
|
Comment::create(&conn, &child_comment_form, Some(&new_parent_comment.path)).unwrap();
|
||||||
comment_like.comment_id = new_parent_comment.id;
|
comment_like.comment_id = new_parent_comment.id;
|
||||||
CommentLike::like(&conn, &comment_like).unwrap();
|
CommentLike::like(&conn, &comment_like).unwrap();
|
||||||
let after_comment_add = PersonAggregates::read(&conn, inserted_person.id).unwrap();
|
let after_comment_add = PersonAggregates::read(&conn, inserted_person.id).unwrap();
|
||||||
|
|
|
@ -70,17 +70,17 @@ mod tests {
|
||||||
..CommentForm::default()
|
..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 {
|
let child_comment_form = CommentForm {
|
||||||
content: "A test comment".into(),
|
content: "A test comment".into(),
|
||||||
creator_id: inserted_person.id,
|
creator_id: inserted_person.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: Some(inserted_comment.id),
|
|
||||||
..CommentForm::default()
|
..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 {
|
let post_like = PostLikeForm {
|
||||||
post_id: inserted_post.id,
|
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.upvotes);
|
||||||
assert_eq!(1, post_aggs_after_dislike.downvotes);
|
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_comment.id).unwrap();
|
||||||
|
Comment::delete(&conn, inserted_child_comment.id).unwrap();
|
||||||
let after_comment_delete = PostAggregates::read(&conn, inserted_post.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.comments);
|
||||||
assert_eq!(0, after_comment_delete.score);
|
assert_eq!(0, after_comment_delete.score);
|
||||||
|
|
|
@ -72,17 +72,17 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Insert two of those comments
|
// 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 {
|
let child_comment_form = CommentForm {
|
||||||
content: "A test comment".into(),
|
content: "A test comment".into(),
|
||||||
creator_id: inserted_person.id,
|
creator_id: inserted_person.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: Some(inserted_comment.id),
|
|
||||||
..CommentForm::default()
|
..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();
|
let site_aggregates_before_delete = SiteAggregates::read(&conn).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ pub struct CommentAggregates {
|
||||||
pub upvotes: i64,
|
pub upvotes: i64,
|
||||||
pub downvotes: i64,
|
pub downvotes: i64,
|
||||||
pub published: chrono::NaiveDateTime,
|
pub published: chrono::NaiveDateTime,
|
||||||
|
pub child_count: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::{
|
||||||
utils::naive_now,
|
utils::naive_now,
|
||||||
};
|
};
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
|
use diesel_ltree::Ltree;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
impl Comment {
|
impl Comment {
|
||||||
|
@ -74,17 +75,6 @@ impl Comment {
|
||||||
.get_results::<Self>(conn)
|
.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(
|
pub fn update_content(
|
||||||
conn: &PgConnection,
|
conn: &PgConnection,
|
||||||
comment_id: CommentId,
|
comment_id: CommentId,
|
||||||
|
@ -96,14 +86,71 @@ impl Comment {
|
||||||
.get_result::<Self>(conn)
|
.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::*;
|
use crate::schema::comment::dsl::*;
|
||||||
insert_into(comment)
|
|
||||||
|
// Insert, to get the id
|
||||||
|
let inserted_comment = insert_into(comment)
|
||||||
.values(comment_form)
|
.values(comment_form)
|
||||||
.on_conflict(ap_id)
|
.on_conflict(ap_id)
|
||||||
.do_update()
|
.do_update()
|
||||||
.set(comment_form)
|
.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> {
|
pub fn read_from_apub_id(conn: &PgConnection, object_id: Url) -> Result<Option<Self>, Error> {
|
||||||
use crate::schema::comment::dsl::*;
|
use crate::schema::comment::dsl::*;
|
||||||
|
@ -116,6 +163,19 @@ impl Comment {
|
||||||
.map(Into::into),
|
.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 {
|
impl Crud for Comment {
|
||||||
|
@ -131,11 +191,9 @@ impl Crud for Comment {
|
||||||
diesel::delete(comment.find(comment_id)).execute(conn)
|
diesel::delete(comment.find(comment_id)).execute(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
|
/// This is unimplemented, use [[Comment::create]]
|
||||||
use crate::schema::comment::dsl::*;
|
fn create(_conn: &PgConnection, _comment_form: &CommentForm) -> Result<Self, Error> {
|
||||||
insert_into(comment)
|
unimplemented!();
|
||||||
.values(comment_form)
|
|
||||||
.get_result::<Self>(conn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
|
@ -218,6 +276,7 @@ mod tests {
|
||||||
traits::{Crud, Likeable, Saveable},
|
traits::{Crud, Likeable, Saveable},
|
||||||
utils::establish_unpooled_connection,
|
utils::establish_unpooled_connection,
|
||||||
};
|
};
|
||||||
|
use diesel_ltree::Ltree;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -258,7 +317,7 @@ mod tests {
|
||||||
..CommentForm::default()
|
..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 {
|
let expected_comment = Comment {
|
||||||
id: inserted_comment.id,
|
id: inserted_comment.id,
|
||||||
|
@ -267,8 +326,7 @@ mod tests {
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
removed: false,
|
removed: false,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
read: false,
|
path: Ltree(format!("0.{}", inserted_comment.id)),
|
||||||
parent_id: None,
|
|
||||||
published: inserted_comment.published,
|
published: inserted_comment.published,
|
||||||
updated: None,
|
updated: None,
|
||||||
ap_id: inserted_comment.ap_id.to_owned(),
|
ap_id: inserted_comment.ap_id.to_owned(),
|
||||||
|
@ -279,11 +337,12 @@ mod tests {
|
||||||
content: "A child comment".into(),
|
content: "A child comment".into(),
|
||||||
creator_id: inserted_person.id,
|
creator_id: inserted_person.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: Some(inserted_comment.id),
|
// path: Some(text2ltree(inserted_comment.id),
|
||||||
..CommentForm::default()
|
..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
|
// Comment Like
|
||||||
let comment_like_form = CommentLikeForm {
|
let comment_like_form = CommentLikeForm {
|
||||||
|
@ -335,8 +394,8 @@ mod tests {
|
||||||
assert_eq!(expected_comment_like, inserted_comment_like);
|
assert_eq!(expected_comment_like, inserted_comment_like);
|
||||||
assert_eq!(expected_comment_saved, inserted_comment_saved);
|
assert_eq!(expected_comment_saved, inserted_comment_saved);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected_comment.id,
|
format!("0.{}.{}", expected_comment.id, inserted_child_comment.id),
|
||||||
inserted_child_comment.parent_id.unwrap()
|
inserted_child_comment.path.0,
|
||||||
);
|
);
|
||||||
assert_eq!(1, like_removed);
|
assert_eq!(1, like_removed);
|
||||||
assert_eq!(1, saved_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 activity;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
|
pub mod comment_reply;
|
||||||
pub mod comment_report;
|
pub mod comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_block;
|
pub mod community_block;
|
||||||
|
|
|
@ -411,7 +411,7 @@ mod tests {
|
||||||
..CommentForm::default()
|
..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
|
// Now the actual tests
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ mod tests {
|
||||||
..CommentForm::default()
|
..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 {
|
let person_mention_form = PersonMentionForm {
|
||||||
recipient_id: inserted_recipient.id,
|
recipient_id: inserted_recipient.id,
|
||||||
|
|
|
@ -30,6 +30,7 @@ pub enum SortType {
|
||||||
Active,
|
Active,
|
||||||
Hot,
|
Hot,
|
||||||
New,
|
New,
|
||||||
|
Old,
|
||||||
TopDay,
|
TopDay,
|
||||||
TopWeek,
|
TopWeek,
|
||||||
TopMonth,
|
TopMonth,
|
||||||
|
@ -39,6 +40,14 @@ pub enum SortType {
|
||||||
NewComments,
|
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)]
|
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
|
||||||
pub enum ListingType {
|
pub enum ListingType {
|
||||||
All,
|
All,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use diesel_ltree::Ltree;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
fmt,
|
||||||
|
@ -68,12 +69,21 @@ pub struct CommentReportId(i32);
|
||||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||||
pub struct PostReportId(i32);
|
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)]
|
#[repr(transparent)]
|
||||||
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
|
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
|
||||||
#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))]
|
#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))]
|
||||||
#[cfg_attr(feature = "full", sql_type = "diesel::sql_types::Text")]
|
#[cfg_attr(feature = "full", sql_type = "diesel::sql_types::Text")]
|
||||||
pub struct DbUrl(pub(crate) Url);
|
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 {
|
impl Display for DbUrl {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
self.to_owned().0.fmt(f)
|
self.to_owned().0.fmt(f)
|
||||||
|
|
|
@ -11,19 +11,21 @@ table! {
|
||||||
}
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
|
use diesel_ltree::sql_types::Ltree;
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
|
||||||
comment (id) {
|
comment (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
creator_id -> Int4,
|
creator_id -> Int4,
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
parent_id -> Nullable<Int4>,
|
|
||||||
content -> Text,
|
content -> Text,
|
||||||
removed -> Bool,
|
removed -> Bool,
|
||||||
read -> Bool,
|
|
||||||
published -> Timestamp,
|
published -> Timestamp,
|
||||||
updated -> Nullable<Timestamp>,
|
updated -> Nullable<Timestamp>,
|
||||||
deleted -> Bool,
|
deleted -> Bool,
|
||||||
ap_id -> Varchar,
|
ap_id -> Varchar,
|
||||||
local -> Bool,
|
local -> Bool,
|
||||||
|
path -> Ltree,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +37,7 @@ table! {
|
||||||
upvotes -> Int8,
|
upvotes -> Int8,
|
||||||
downvotes -> Int8,
|
downvotes -> Int8,
|
||||||
published -> Timestamp,
|
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! {
|
table! {
|
||||||
post (id) {
|
post (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -501,23 +514,6 @@ table! {
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are necessary since diesel doesn't have self joins / aliases
|
// 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! {
|
table! {
|
||||||
person_alias_1 (id) {
|
person_alias_1 (id) {
|
||||||
id -> Int4,
|
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!(person_mention -> person_alias_1 (recipient_id));
|
||||||
|
joinable!(comment_reply -> person_alias_1 (recipient_id));
|
||||||
joinable!(post -> person_alias_1 (creator_id));
|
joinable!(post -> person_alias_1 (creator_id));
|
||||||
joinable!(comment -> 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_ban -> person (person_id));
|
||||||
joinable!(person_mention -> comment (comment_id));
|
joinable!(person_mention -> comment (comment_id));
|
||||||
joinable!(person_mention -> person (recipient_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 -> community (community_id));
|
||||||
joinable!(post -> person (creator_id));
|
joinable!(post -> person (creator_id));
|
||||||
joinable!(post_aggregates -> post (post_id));
|
joinable!(post_aggregates -> post (post_id));
|
||||||
|
@ -751,6 +748,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
person_ban,
|
person_ban,
|
||||||
person_block,
|
person_block,
|
||||||
person_mention,
|
person_mention,
|
||||||
|
comment_reply,
|
||||||
post,
|
post,
|
||||||
post_aggregates,
|
post_aggregates,
|
||||||
post_like,
|
post_like,
|
||||||
|
@ -760,7 +758,6 @@ allow_tables_to_appear_in_same_query!(
|
||||||
private_message,
|
private_message,
|
||||||
site,
|
site,
|
||||||
site_aggregates,
|
site_aggregates,
|
||||||
comment_alias_1,
|
|
||||||
person_alias_1,
|
person_alias_1,
|
||||||
person_alias_2,
|
person_alias_2,
|
||||||
admin_purge_comment,
|
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};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use crate::schema::{comment, comment_alias_1, comment_like, comment_saved};
|
use crate::schema::{comment, 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;
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
|
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
|
||||||
|
@ -19,34 +13,15 @@ pub struct Comment {
|
||||||
pub id: CommentId,
|
pub id: CommentId,
|
||||||
pub creator_id: PersonId,
|
pub creator_id: PersonId,
|
||||||
pub post_id: PostId,
|
pub post_id: PostId,
|
||||||
pub parent_id: Option<CommentId>,
|
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub removed: bool,
|
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 published: chrono::NaiveDateTime,
|
||||||
pub updated: Option<chrono::NaiveDateTime>,
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
pub deleted: bool,
|
pub deleted: bool,
|
||||||
pub ap_id: DbUrl,
|
pub ap_id: DbUrl,
|
||||||
pub local: bool,
|
pub local: bool,
|
||||||
|
#[serde(with = "LtreeDef")]
|
||||||
|
pub path: Ltree,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
|
@ -56,9 +31,7 @@ pub struct CommentForm {
|
||||||
pub creator_id: PersonId,
|
pub creator_id: PersonId,
|
||||||
pub post_id: PostId,
|
pub post_id: PostId,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub parent_id: Option<CommentId>,
|
|
||||||
pub removed: Option<bool>,
|
pub removed: Option<bool>,
|
||||||
pub read: Option<bool>,
|
|
||||||
pub published: Option<chrono::NaiveDateTime>,
|
pub published: Option<chrono::NaiveDateTime>,
|
||||||
pub updated: Option<chrono::NaiveDateTime>,
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
pub deleted: Option<bool>,
|
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")]
|
#[cfg(feature = "full")]
|
||||||
pub mod activity;
|
pub mod activity;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
|
pub mod comment_reply;
|
||||||
pub mod comment_report;
|
pub mod comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_block;
|
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 activitypub_federation::{core::object_id::ObjectId, traits::ApubObject};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{
|
use diesel::{
|
||||||
|
@ -118,6 +118,19 @@ pub fn naive_now() -> NaiveDateTime {
|
||||||
chrono::prelude::Utc::now().naive_utc()
|
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(|| {
|
static EMAIL_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
|
Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
|
||||||
.expect("compile email regex")
|
.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 }
|
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"], optional = true }
|
||||||
serde = { version = "1.0.136", features = ["derive"] }
|
serde = { version = "1.0.136", features = ["derive"] }
|
||||||
tracing = { version = "0.1.32", optional = true }
|
tracing = { version = "0.1.32", optional = true }
|
||||||
|
diesel_ltree = "0.2.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = "0.6.0"
|
serial_test = "0.6.0"
|
||||||
|
|
|
@ -377,7 +377,7 @@ mod tests {
|
||||||
..CommentForm::default()
|
..CommentForm::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
|
let inserted_comment = Comment::create(&conn, &comment_form, None).unwrap();
|
||||||
|
|
||||||
// sara reports
|
// sara reports
|
||||||
let sara_report_form = CommentReportForm {
|
let sara_report_form = CommentReportForm {
|
||||||
|
@ -472,6 +472,7 @@ mod tests {
|
||||||
upvotes: 0,
|
upvotes: 0,
|
||||||
downvotes: 0,
|
downvotes: 0,
|
||||||
published: agg.published,
|
published: agg.published,
|
||||||
|
child_count: 0,
|
||||||
},
|
},
|
||||||
my_vote: None,
|
my_vote: None,
|
||||||
resolver: None,
|
resolver: None,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::structs::CommentView;
|
use crate::structs::CommentView;
|
||||||
use diesel::{dsl::*, result::Error, *};
|
use diesel::{dsl::*, result::Error, *};
|
||||||
|
use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::CommentAggregates,
|
aggregates::structs::CommentAggregates,
|
||||||
newtypes::{CommentId, CommunityId, DbUrl, PersonId, PostId},
|
newtypes::{CommentId, CommunityId, DbUrl, PersonId, PostId},
|
||||||
schema::{
|
schema::{
|
||||||
comment,
|
comment,
|
||||||
comment_aggregates,
|
comment_aggregates,
|
||||||
comment_alias_1,
|
|
||||||
comment_like,
|
comment_like,
|
||||||
comment_saved,
|
comment_saved,
|
||||||
community,
|
community,
|
||||||
|
@ -14,28 +14,25 @@ use lemmy_db_schema::{
|
||||||
community_follower,
|
community_follower,
|
||||||
community_person_ban,
|
community_person_ban,
|
||||||
person,
|
person,
|
||||||
person_alias_1,
|
|
||||||
person_block,
|
person_block,
|
||||||
post,
|
post,
|
||||||
},
|
},
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentAlias1, CommentSaved},
|
comment::{Comment, CommentSaved},
|
||||||
community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
|
community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
|
||||||
person::{Person, PersonAlias1, PersonSafe, PersonSafeAlias1},
|
person::{Person, PersonSafe},
|
||||||
person_block::PersonBlock,
|
person_block::PersonBlock,
|
||||||
post::Post,
|
post::Post,
|
||||||
},
|
},
|
||||||
traits::{MaybeOptional, ToSafe, ViewToVec},
|
traits::{MaybeOptional, ToSafe, ViewToVec},
|
||||||
utils::{functions::hot_rank, fuzzy_search, limit_and_offset_unlimited},
|
utils::{functions::hot_rank, fuzzy_search, limit_and_offset_unlimited},
|
||||||
|
CommentSortType,
|
||||||
ListingType,
|
ListingType,
|
||||||
SortType,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type CommentViewTuple = (
|
type CommentViewTuple = (
|
||||||
Comment,
|
Comment,
|
||||||
PersonSafe,
|
PersonSafe,
|
||||||
Option<CommentAlias1>,
|
|
||||||
Option<PersonSafeAlias1>,
|
|
||||||
Post,
|
Post,
|
||||||
CommunitySafe,
|
CommunitySafe,
|
||||||
CommentAggregates,
|
CommentAggregates,
|
||||||
|
@ -58,8 +55,6 @@ impl CommentView {
|
||||||
let (
|
let (
|
||||||
comment,
|
comment,
|
||||||
creator,
|
creator,
|
||||||
_parent_comment,
|
|
||||||
recipient,
|
|
||||||
post,
|
post,
|
||||||
community,
|
community,
|
||||||
counts,
|
counts,
|
||||||
|
@ -71,9 +66,6 @@ impl CommentView {
|
||||||
) = comment::table
|
) = comment::table
|
||||||
.find(comment_id)
|
.find(comment_id)
|
||||||
.inner_join(person::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(post::table)
|
||||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||||
.inner_join(comment_aggregates::table)
|
.inner_join(comment_aggregates::table)
|
||||||
|
@ -120,8 +112,6 @@ impl CommentView {
|
||||||
.select((
|
.select((
|
||||||
comment::all_columns,
|
comment::all_columns,
|
||||||
Person::safe_columns_tuple(),
|
Person::safe_columns_tuple(),
|
||||||
comment_alias_1::all_columns.nullable(),
|
|
||||||
PersonAlias1::safe_columns_tuple().nullable(),
|
|
||||||
post::all_columns,
|
post::all_columns,
|
||||||
Community::safe_columns_tuple(),
|
Community::safe_columns_tuple(),
|
||||||
comment_aggregates::all_columns,
|
comment_aggregates::all_columns,
|
||||||
|
@ -143,7 +133,6 @@ impl CommentView {
|
||||||
|
|
||||||
Ok(CommentView {
|
Ok(CommentView {
|
||||||
comment,
|
comment,
|
||||||
recipient,
|
|
||||||
post,
|
post,
|
||||||
creator,
|
creator,
|
||||||
community,
|
community,
|
||||||
|
@ -155,73 +144,24 @@ impl CommentView {
|
||||||
my_vote,
|
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> {
|
pub struct CommentQueryBuilder<'a> {
|
||||||
conn: &'a PgConnection,
|
conn: &'a PgConnection,
|
||||||
listing_type: Option<ListingType>,
|
listing_type: Option<ListingType>,
|
||||||
sort: Option<SortType>,
|
sort: Option<CommentSortType>,
|
||||||
community_id: Option<CommunityId>,
|
community_id: Option<CommunityId>,
|
||||||
community_actor_id: Option<DbUrl>,
|
community_actor_id: Option<DbUrl>,
|
||||||
post_id: Option<PostId>,
|
post_id: Option<PostId>,
|
||||||
|
parent_path: Option<Ltree>,
|
||||||
creator_id: Option<PersonId>,
|
creator_id: Option<PersonId>,
|
||||||
recipient_id: Option<PersonId>,
|
|
||||||
my_person_id: Option<PersonId>,
|
my_person_id: Option<PersonId>,
|
||||||
search_term: Option<String>,
|
search_term: Option<String>,
|
||||||
saved_only: Option<bool>,
|
saved_only: Option<bool>,
|
||||||
unread_only: Option<bool>,
|
|
||||||
show_bot_accounts: Option<bool>,
|
show_bot_accounts: Option<bool>,
|
||||||
page: Option<i64>,
|
page: Option<i64>,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
|
max_depth: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CommentQueryBuilder<'a> {
|
impl<'a> CommentQueryBuilder<'a> {
|
||||||
|
@ -233,15 +173,15 @@ impl<'a> CommentQueryBuilder<'a> {
|
||||||
community_id: None,
|
community_id: None,
|
||||||
community_actor_id: None,
|
community_actor_id: None,
|
||||||
post_id: None,
|
post_id: None,
|
||||||
|
parent_path: None,
|
||||||
creator_id: None,
|
creator_id: None,
|
||||||
recipient_id: None,
|
|
||||||
my_person_id: None,
|
my_person_id: None,
|
||||||
search_term: None,
|
search_term: None,
|
||||||
saved_only: None,
|
saved_only: None,
|
||||||
unread_only: None,
|
|
||||||
show_bot_accounts: None,
|
show_bot_accounts: None,
|
||||||
page: None,
|
page: None,
|
||||||
limit: None,
|
limit: None,
|
||||||
|
max_depth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +190,7 @@ impl<'a> CommentQueryBuilder<'a> {
|
||||||
self
|
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.sort = sort.get_optional();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -265,11 +205,6 @@ impl<'a> CommentQueryBuilder<'a> {
|
||||||
self
|
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 {
|
pub fn community_id<T: MaybeOptional<CommunityId>>(mut self, community_id: T) -> Self {
|
||||||
self.community_id = community_id.get_optional();
|
self.community_id = community_id.get_optional();
|
||||||
self
|
self
|
||||||
|
@ -295,13 +230,13 @@ impl<'a> CommentQueryBuilder<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unread_only<T: MaybeOptional<bool>>(mut self, unread_only: T) -> Self {
|
pub fn show_bot_accounts<T: MaybeOptional<bool>>(mut self, show_bot_accounts: T) -> Self {
|
||||||
self.unread_only = unread_only.get_optional();
|
self.show_bot_accounts = show_bot_accounts.get_optional();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_bot_accounts<T: MaybeOptional<bool>>(mut self, show_bot_accounts: T) -> Self {
|
pub fn parent_path<T: MaybeOptional<Ltree>>(mut self, parent_path: T) -> Self {
|
||||||
self.show_bot_accounts = show_bot_accounts.get_optional();
|
self.parent_path = parent_path.get_optional();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,6 +250,11 @@ impl<'a> CommentQueryBuilder<'a> {
|
||||||
self
|
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> {
|
pub fn list(self) -> Result<Vec<CommentView>, Error> {
|
||||||
use diesel::dsl::*;
|
use diesel::dsl::*;
|
||||||
|
|
||||||
|
@ -323,9 +263,6 @@ impl<'a> CommentQueryBuilder<'a> {
|
||||||
|
|
||||||
let mut query = comment::table
|
let mut query = comment::table
|
||||||
.inner_join(person::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(post::table)
|
||||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||||
.inner_join(comment_aggregates::table)
|
.inner_join(comment_aggregates::table)
|
||||||
|
@ -379,8 +316,6 @@ impl<'a> CommentQueryBuilder<'a> {
|
||||||
.select((
|
.select((
|
||||||
comment::all_columns,
|
comment::all_columns,
|
||||||
Person::safe_columns_tuple(),
|
Person::safe_columns_tuple(),
|
||||||
comment_alias_1::all_columns.nullable(),
|
|
||||||
PersonAlias1::safe_columns_tuple().nullable(),
|
|
||||||
post::all_columns,
|
post::all_columns,
|
||||||
Community::safe_columns_tuple(),
|
Community::safe_columns_tuple(),
|
||||||
comment_aggregates::all_columns,
|
comment_aggregates::all_columns,
|
||||||
|
@ -392,24 +327,6 @@ impl<'a> CommentQueryBuilder<'a> {
|
||||||
))
|
))
|
||||||
.into_boxed();
|
.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 {
|
if let Some(creator_id) = self.creator_id {
|
||||||
query = query.filter(comment::creator_id.eq(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));
|
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 {
|
if let Some(search_term) = self.search_term {
|
||||||
query = query.filter(comment::content.ilike(fuzzy_search(&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 = 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
|
// Don't show blocked communities or persons
|
||||||
if self.my_person_id.is_some() {
|
if self.my_person_id.is_some() {
|
||||||
query = query.filter(community_block::person_id.is_null());
|
query = query.filter(community_block::person_id.is_null());
|
||||||
query = query.filter(person_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.
|
// A Max depth given means its a tree fetch
|
||||||
let (limit, offset) = limit_and_offset_unlimited(self.page, self.limit);
|
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
|
// Note: deleted and removed comments are done on the front side
|
||||||
let res = query
|
let res = query
|
||||||
|
@ -509,15 +441,14 @@ impl ViewToVec for CommentView {
|
||||||
.map(|a| Self {
|
.map(|a| Self {
|
||||||
comment: a.0.to_owned(),
|
comment: a.0.to_owned(),
|
||||||
creator: a.1.to_owned(),
|
creator: a.1.to_owned(),
|
||||||
recipient: a.3.to_owned(),
|
post: a.2.to_owned(),
|
||||||
post: a.4.to_owned(),
|
community: a.3.to_owned(),
|
||||||
community: a.5.to_owned(),
|
counts: a.4.to_owned(),
|
||||||
counts: a.6.to_owned(),
|
creator_banned_from_community: a.5.is_some(),
|
||||||
creator_banned_from_community: a.7.is_some(),
|
subscribed: CommunityFollower::to_subscribed_type(&a.6),
|
||||||
subscribed: CommunityFollower::to_subscribed_type(&a.8),
|
saved: a.7.is_some(),
|
||||||
saved: a.9.is_some(),
|
creator_blocked: a.8.is_some(),
|
||||||
creator_blocked: a.10.is_some(),
|
my_vote: a.9,
|
||||||
my_vote: a.11,
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<Self>>()
|
.collect::<Vec<Self>>()
|
||||||
}
|
}
|
||||||
|
@ -574,24 +505,72 @@ mod tests {
|
||||||
|
|
||||||
let inserted_post = Post::create(&conn, &new_post).unwrap();
|
let inserted_post = Post::create(&conn, &new_post).unwrap();
|
||||||
|
|
||||||
let comment_form = CommentForm {
|
// Create a comment tree with this hierarchy
|
||||||
content: "A test comment 32".into(),
|
// 0
|
||||||
|
// \ \
|
||||||
|
// 1 2
|
||||||
|
// \
|
||||||
|
// 3 4
|
||||||
|
// \
|
||||||
|
// 5
|
||||||
|
let comment_form_0 = CommentForm {
|
||||||
|
content: "Comment 0".into(),
|
||||||
creator_id: inserted_person.id,
|
creator_id: inserted_person.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
..CommentForm::default()
|
..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 {
|
let comment_form_1 = CommentForm {
|
||||||
content: "A test blocked comment".into(),
|
content: "Comment 1, A test blocked comment".into(),
|
||||||
creator_id: inserted_person_2.id,
|
creator_id: inserted_person_2.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: Some(inserted_comment.id),
|
|
||||||
..CommentForm::default()
|
..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 {
|
let timmy_blocks_sara_form = PersonBlockForm {
|
||||||
person_id: inserted_person.id,
|
person_id: inserted_person.id,
|
||||||
|
@ -610,7 +589,7 @@ mod tests {
|
||||||
assert_eq!(expected_block, inserted_block);
|
assert_eq!(expected_block, inserted_block);
|
||||||
|
|
||||||
let comment_like_form = CommentLikeForm {
|
let comment_like_form = CommentLikeForm {
|
||||||
comment_id: inserted_comment.id,
|
comment_id: inserted_comment_0.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
person_id: inserted_person.id,
|
person_id: inserted_person.id,
|
||||||
score: 1,
|
score: 1,
|
||||||
|
@ -618,8 +597,9 @@ mod tests {
|
||||||
|
|
||||||
let _inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
|
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 {
|
let expected_comment_view_no_person = CommentView {
|
||||||
creator_banned_from_community: false,
|
creator_banned_from_community: false,
|
||||||
my_vote: None,
|
my_vote: None,
|
||||||
|
@ -627,18 +607,17 @@ mod tests {
|
||||||
saved: false,
|
saved: false,
|
||||||
creator_blocked: false,
|
creator_blocked: false,
|
||||||
comment: Comment {
|
comment: Comment {
|
||||||
id: inserted_comment.id,
|
id: inserted_comment_0.id,
|
||||||
content: "A test comment 32".into(),
|
content: "Comment 0".into(),
|
||||||
creator_id: inserted_person.id,
|
creator_id: inserted_person.id,
|
||||||
post_id: inserted_post.id,
|
post_id: inserted_post.id,
|
||||||
parent_id: None,
|
|
||||||
removed: false,
|
removed: false,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
read: false,
|
published: inserted_comment_0.published,
|
||||||
published: inserted_comment.published,
|
ap_id: inserted_comment_0.ap_id,
|
||||||
ap_id: inserted_comment.ap_id,
|
|
||||||
updated: None,
|
updated: None,
|
||||||
local: true,
|
local: true,
|
||||||
|
path: top_path,
|
||||||
},
|
},
|
||||||
creator: PersonSafe {
|
creator: PersonSafe {
|
||||||
id: inserted_person.id,
|
id: inserted_person.id,
|
||||||
|
@ -660,7 +639,6 @@ mod tests {
|
||||||
matrix_user_id: None,
|
matrix_user_id: None,
|
||||||
ban_expires: None,
|
ban_expires: None,
|
||||||
},
|
},
|
||||||
recipient: None,
|
|
||||||
post: Post {
|
post: Post {
|
||||||
id: inserted_post.id,
|
id: inserted_post.id,
|
||||||
name: inserted_post.name.to_owned(),
|
name: inserted_post.name.to_owned(),
|
||||||
|
@ -701,11 +679,12 @@ mod tests {
|
||||||
},
|
},
|
||||||
counts: CommentAggregates {
|
counts: CommentAggregates {
|
||||||
id: agg.id,
|
id: agg.id,
|
||||||
comment_id: inserted_comment.id,
|
comment_id: inserted_comment_0.id,
|
||||||
score: 1,
|
score: 1,
|
||||||
upvotes: 1,
|
upvotes: 1,
|
||||||
downvotes: 0,
|
downvotes: 0,
|
||||||
published: agg.published,
|
published: agg.published,
|
||||||
|
child_count: 5,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -717,38 +696,96 @@ mod tests {
|
||||||
.list()
|
.list()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
expected_comment_view_no_person,
|
||||||
|
read_comment_views_no_person[0]
|
||||||
|
);
|
||||||
|
|
||||||
let read_comment_views_with_person = CommentQueryBuilder::create(&conn)
|
let read_comment_views_with_person = CommentQueryBuilder::create(&conn)
|
||||||
.post_id(inserted_post.id)
|
.post_id(inserted_post.id)
|
||||||
.my_person_id(inserted_person.id)
|
.my_person_id(inserted_person.id)
|
||||||
.list()
|
.list()
|
||||||
.unwrap();
|
.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!(
|
assert_eq!(
|
||||||
expected_comment_view_with_person,
|
expected_comment_view_with_person,
|
||||||
read_comment_views_with_person[0]
|
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
|
// Make sure block set the creator blocked
|
||||||
assert!(read_comment_from_blocked_person.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, num_deleted);
|
||||||
assert_eq!(1, like_removed);
|
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(hot_rank(post_aggregates::score, post_aggregates::published).desc())
|
||||||
.then_order_by(post_aggregates::published.desc()),
|
.then_order_by(post_aggregates::published.desc()),
|
||||||
SortType::New => query.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::NewComments => query.then_order_by(post_aggregates::newest_comment_time.desc()),
|
||||||
SortType::MostComments => query
|
SortType::MostComments => query
|
||||||
.then_order_by(post_aggregates::comments.desc())
|
.then_order_by(post_aggregates::comments.desc())
|
||||||
|
|
|
@ -34,7 +34,6 @@ pub struct CommentReportView {
|
||||||
pub struct CommentView {
|
pub struct CommentView {
|
||||||
pub comment: Comment,
|
pub comment: Comment,
|
||||||
pub creator: PersonSafe,
|
pub creator: PersonSafe,
|
||||||
pub recipient: Option<PersonSafeAlias1>, // Left joins to comment and person
|
|
||||||
pub post: Post,
|
pub post: Post,
|
||||||
pub community: CommunitySafe,
|
pub community: CommunitySafe,
|
||||||
pub counts: CommentAggregates,
|
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")]
|
#[cfg(feature = "full")]
|
||||||
|
pub mod comment_reply_view;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod community_block_view;
|
pub mod community_block_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod community_follower_view;
|
pub mod community_follower_view;
|
||||||
|
|
|
@ -27,7 +27,7 @@ use lemmy_db_schema::{
|
||||||
},
|
},
|
||||||
traits::{MaybeOptional, ToSafe, ViewToVec},
|
traits::{MaybeOptional, ToSafe, ViewToVec},
|
||||||
utils::{functions::hot_rank, limit_and_offset},
|
utils::{functions::hot_rank, limit_and_offset},
|
||||||
SortType,
|
CommentSortType,
|
||||||
};
|
};
|
||||||
|
|
||||||
type PersonMentionViewTuple = (
|
type PersonMentionViewTuple = (
|
||||||
|
@ -163,8 +163,9 @@ pub struct PersonMentionQueryBuilder<'a> {
|
||||||
conn: &'a PgConnection,
|
conn: &'a PgConnection,
|
||||||
my_person_id: Option<PersonId>,
|
my_person_id: Option<PersonId>,
|
||||||
recipient_id: Option<PersonId>,
|
recipient_id: Option<PersonId>,
|
||||||
sort: Option<SortType>,
|
sort: Option<CommentSortType>,
|
||||||
unread_only: Option<bool>,
|
unread_only: Option<bool>,
|
||||||
|
show_bot_accounts: Option<bool>,
|
||||||
page: Option<i64>,
|
page: Option<i64>,
|
||||||
limit: Option<i64>,
|
limit: Option<i64>,
|
||||||
}
|
}
|
||||||
|
@ -177,12 +178,13 @@ impl<'a> PersonMentionQueryBuilder<'a> {
|
||||||
recipient_id: None,
|
recipient_id: None,
|
||||||
sort: None,
|
sort: None,
|
||||||
unread_only: None,
|
unread_only: None,
|
||||||
|
show_bot_accounts: None,
|
||||||
page: None,
|
page: None,
|
||||||
limit: 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.sort = sort.get_optional();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -192,6 +194,11 @@ impl<'a> PersonMentionQueryBuilder<'a> {
|
||||||
self
|
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 {
|
pub fn recipient_id<T: MaybeOptional<PersonId>>(mut self, recipient_id: T) -> Self {
|
||||||
self.recipient_id = recipient_id.get_optional();
|
self.recipient_id = recipient_id.get_optional();
|
||||||
self
|
self
|
||||||
|
@ -289,26 +296,17 @@ impl<'a> PersonMentionQueryBuilder<'a> {
|
||||||
query = query.filter(person_mention::read.eq(false));
|
query = query.filter(person_mention::read.eq(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
query = match self.sort.unwrap_or(SortType::Hot) {
|
if !self.show_bot_accounts.unwrap_or(true) {
|
||||||
SortType::Hot | SortType::Active => query
|
query = query.filter(person::bot_account.eq(false));
|
||||||
.order_by(hot_rank(comment_aggregates::score, comment_aggregates::published).desc())
|
};
|
||||||
|
|
||||||
|
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()),
|
.then_order_by(comment_aggregates::published.desc()),
|
||||||
SortType::New | SortType::MostComments | SortType::NewComments => {
|
CommentSortType::New => query.then_order_by(comment::published.desc()),
|
||||||
query.order_by(comment::published.desc())
|
CommentSortType::Old => query.then_order_by(comment::published.asc()),
|
||||||
}
|
CommentSortType::Top => query.order_by(comment_aggregates::score.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()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
|
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
|
||||||
|
|
|
@ -109,6 +109,7 @@ impl<'a> PersonQueryBuilder<'a> {
|
||||||
SortType::New | SortType::MostComments | SortType::NewComments => {
|
SortType::New | SortType::MostComments | SortType::NewComments => {
|
||||||
query.order_by(person::published.desc())
|
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::TopAll => query.order_by(person_aggregates::comment_score.desc()),
|
||||||
SortType::TopYear => query
|
SortType::TopYear => query
|
||||||
.filter(person::published.gt(now - 1.years()))
|
.filter(person::published.gt(now - 1.years()))
|
||||||
|
|
|
@ -2,6 +2,7 @@ use lemmy_db_schema::{
|
||||||
aggregates::structs::{CommentAggregates, CommunityAggregates, PersonAggregates},
|
aggregates::structs::{CommentAggregates, CommunityAggregates, PersonAggregates},
|
||||||
source::{
|
source::{
|
||||||
comment::Comment,
|
comment::Comment,
|
||||||
|
comment_reply::CommentReply,
|
||||||
community::CommunitySafe,
|
community::CommunitySafe,
|
||||||
person::{PersonSafe, PersonSafeAlias1},
|
person::{PersonSafe, PersonSafeAlias1},
|
||||||
person_mention::PersonMention,
|
person_mention::PersonMention,
|
||||||
|
@ -65,6 +66,22 @@ pub struct PersonMentionView {
|
||||||
pub my_vote: Option<i16>, // Left join to CommentLike
|
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)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct PersonViewSafe {
|
pub struct PersonViewSafe {
|
||||||
pub person: PersonSafe,
|
pub person: PersonSafe,
|
||||||
|
|
|
@ -7,17 +7,18 @@ use lemmy_db_schema::{
|
||||||
newtypes::LocalUserId,
|
newtypes::LocalUserId,
|
||||||
source::{community::Community, local_user::LocalUser, person::Person},
|
source::{community::Community, local_user::LocalUser, person::Person},
|
||||||
traits::{ApubActor, Crud},
|
traits::{ApubActor, Crud},
|
||||||
|
CommentSortType,
|
||||||
ListingType,
|
ListingType,
|
||||||
SortType,
|
SortType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{
|
use lemmy_db_views::{
|
||||||
comment_view::CommentQueryBuilder,
|
|
||||||
post_view::PostQueryBuilder,
|
post_view::PostQueryBuilder,
|
||||||
structs::{CommentView, PostView, SiteView},
|
structs::{PostView, SiteView},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
|
comment_reply_view::CommentReplyQueryBuilder,
|
||||||
person_mention_view::PersonMentionQueryBuilder,
|
person_mention_view::PersonMentionQueryBuilder,
|
||||||
structs::PersonMentionView,
|
structs::{CommentReplyView, PersonMentionView},
|
||||||
};
|
};
|
||||||
use lemmy_utils::{claims::Claims, error::LemmyError, utils::markdown_to_html};
|
use lemmy_utils::{claims::Claims, error::LemmyError, utils::markdown_to_html};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
@ -284,9 +285,9 @@ fn get_feed_inbox(
|
||||||
let person_id = local_user.person_id;
|
let person_id = local_user.person_id;
|
||||||
let show_bot_accounts = local_user.show_bot_accounts;
|
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)
|
.recipient_id(person_id)
|
||||||
.my_person_id(person_id)
|
.my_person_id(person_id)
|
||||||
.show_bot_accounts(show_bot_accounts)
|
.show_bot_accounts(show_bot_accounts)
|
||||||
|
@ -297,6 +298,7 @@ fn get_feed_inbox(
|
||||||
let mentions = PersonMentionQueryBuilder::create(conn)
|
let mentions = PersonMentionQueryBuilder::create(conn)
|
||||||
.recipient_id(person_id)
|
.recipient_id(person_id)
|
||||||
.my_person_id(person_id)
|
.my_person_id(person_id)
|
||||||
|
.show_bot_accounts(show_bot_accounts)
|
||||||
.sort(sort)
|
.sort(sort)
|
||||||
.limit(RSS_FETCH_LIMIT)
|
.limit(RSS_FETCH_LIMIT)
|
||||||
.list()?;
|
.list()?;
|
||||||
|
@ -319,7 +321,7 @@ fn get_feed_inbox(
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn create_reply_and_mention_items(
|
fn create_reply_and_mention_items(
|
||||||
replies: Vec<CommentView>,
|
replies: Vec<CommentReplyView>,
|
||||||
mentions: Vec<PersonMentionView>,
|
mentions: Vec<PersonMentionView>,
|
||||||
protocol_and_hostname: &str,
|
protocol_and_hostname: &str,
|
||||||
) -> Result<Vec<Item>, LemmyError> {
|
) -> Result<Vec<Item>, LemmyError> {
|
||||||
|
|
|
@ -440,7 +440,7 @@ impl ChatServer {
|
||||||
|
|
||||||
fn sendit(&self, message: &str, id: ConnectionId) {
|
fn sendit(&self, message: &str, id: ConnectionId) {
|
||||||
if let Some(info) = self.sessions.get(&id) {
|
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 {
|
pub enum UserOperation {
|
||||||
Login,
|
Login,
|
||||||
GetCaptcha,
|
GetCaptcha,
|
||||||
MarkCommentAsRead,
|
|
||||||
SaveComment,
|
SaveComment,
|
||||||
CreateCommentLike,
|
CreateCommentLike,
|
||||||
CreateCommentReport,
|
CreateCommentReport,
|
||||||
|
@ -116,6 +115,7 @@ pub enum UserOperation {
|
||||||
GetReplies,
|
GetReplies,
|
||||||
GetPersonMentions,
|
GetPersonMentions,
|
||||||
MarkPersonMentionAsRead,
|
MarkPersonMentionAsRead,
|
||||||
|
MarkCommentReplyAsRead,
|
||||||
GetModlog,
|
GetModlog,
|
||||||
BanFromCommunity,
|
BanFromCommunity,
|
||||||
AddModToCommunity,
|
AddModToCommunity,
|
||||||
|
|
|
@ -14,6 +14,7 @@ use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId},
|
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId},
|
||||||
source::{
|
source::{
|
||||||
comment::Comment,
|
comment::Comment,
|
||||||
|
comment_reply::{CommentReply, CommentReplyForm},
|
||||||
person::Person,
|
person::Person,
|
||||||
person_mention::{PersonMention, PersonMentionForm},
|
person_mention::{PersonMention, PersonMentionForm},
|
||||||
post::Post,
|
post::Post,
|
||||||
|
@ -223,71 +224,97 @@ pub async fn send_local_notifs(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send notifs to the parent commenter / poster
|
// Send comment_reply to the parent commenter / poster
|
||||||
match comment.parent_id {
|
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
||||||
Some(parent_id) => {
|
let parent_comment = blocking(context.pool(), move |conn| {
|
||||||
let parent_comment =
|
Comment::read(conn, parent_comment_id)
|
||||||
blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await?;
|
})
|
||||||
if let Ok(parent_comment) = parent_comment {
|
.await??;
|
||||||
// Get the parent commenter local_user
|
|
||||||
let parent_creator_id = parent_comment.creator_id;
|
|
||||||
|
|
||||||
// Only add to recipients if that person isn't blocked
|
// Get the parent commenter local_user
|
||||||
let creator_blocked = check_person_block(person.id, parent_creator_id, context.pool())
|
let parent_creator_id = parent_comment.creator_id;
|
||||||
.await
|
|
||||||
.is_err();
|
|
||||||
|
|
||||||
// Don't send a notif to yourself
|
// Only add to recipients if that person isn't blocked
|
||||||
if parent_comment.creator_id != person.id && !creator_blocked {
|
let creator_blocked = check_person_block(person.id, parent_creator_id, context.pool())
|
||||||
let user_view = blocking(context.pool(), move |conn| {
|
.await
|
||||||
LocalUserView::read_person(conn, parent_creator_id)
|
.is_err();
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
if let Ok(parent_user_view) = user_view {
|
|
||||||
recipient_ids.push(parent_user_view.local_user.id);
|
|
||||||
|
|
||||||
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
|
// Don't send a notif to yourself
|
||||||
None => {
|
if parent_comment.creator_id != person.id && !creator_blocked {
|
||||||
// Only add to recipients if that person isn't blocked
|
let user_view = blocking(context.pool(), move |conn| {
|
||||||
let creator_blocked = check_person_block(person.id, post.creator_id, context.pool())
|
LocalUserView::read_person(conn, parent_creator_id)
|
||||||
.await
|
})
|
||||||
.is_err();
|
.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 comment_reply_form = CommentReplyForm {
|
||||||
let creator_id = post.creator_id;
|
recipient_id: parent_user_view.person.id,
|
||||||
let parent_user = blocking(context.pool(), move |conn| {
|
comment_id: comment.id,
|
||||||
LocalUserView::read_person(conn, creator_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?;
|
.await?
|
||||||
if let Ok(parent_user_view) = parent_user {
|
.ok();
|
||||||
recipient_ids.push(parent_user_view.local_user.id);
|
|
||||||
|
|
||||||
if do_send_email {
|
if do_send_email {
|
||||||
let lang = get_user_lang(&parent_user_view);
|
let lang = get_user_lang(&parent_user_view);
|
||||||
send_email_to_user(
|
send_email_to_user(
|
||||||
&parent_user_view,
|
&parent_user_view,
|
||||||
&lang.notification_post_reply_subject(&person.name),
|
&lang.notification_comment_reply_subject(&person.name),
|
||||||
&lang.notification_post_reply_body(&comment.content, &inbox_link, &person.name),
|
&lang.notification_comment_reply_body(&comment.content, &inbox_link, &person.name),
|
||||||
context.settings(),
|
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)
|
Ok(recipient_ids)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ services:
|
||||||
- lemmy
|
- lemmy
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12-alpine
|
image: postgres:14-alpine
|
||||||
ports:
|
ports:
|
||||||
# use a different port so it doesnt conflict with postgres running on the host
|
# use a different port so it doesnt conflict with postgres running on the host
|
||||||
- "5433:5432"
|
- "5433:5432"
|
||||||
|
|
|
@ -47,7 +47,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "8541:8541"
|
- "8541:8541"
|
||||||
postgres_alpha:
|
postgres_alpha:
|
||||||
image: postgres:12-alpine
|
image: postgres:14-alpine
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=lemmy
|
- POSTGRES_USER=lemmy
|
||||||
- POSTGRES_PASSWORD=password
|
- POSTGRES_PASSWORD=password
|
||||||
|
@ -75,7 +75,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "8551:8551"
|
- "8551:8551"
|
||||||
postgres_beta:
|
postgres_beta:
|
||||||
image: postgres:12-alpine
|
image: postgres:14-alpine
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=lemmy
|
- POSTGRES_USER=lemmy
|
||||||
- POSTGRES_PASSWORD=password
|
- POSTGRES_PASSWORD=password
|
||||||
|
@ -103,7 +103,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "8561:8561"
|
- "8561:8561"
|
||||||
postgres_gamma:
|
postgres_gamma:
|
||||||
image: postgres:12-alpine
|
image: postgres:14-alpine
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=lemmy
|
- POSTGRES_USER=lemmy
|
||||||
- POSTGRES_PASSWORD=password
|
- POSTGRES_PASSWORD=password
|
||||||
|
@ -132,7 +132,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "8571:8571"
|
- "8571:8571"
|
||||||
postgres_delta:
|
postgres_delta:
|
||||||
image: postgres:12-alpine
|
image: postgres:14-alpine
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=lemmy
|
- POSTGRES_USER=lemmy
|
||||||
- POSTGRES_PASSWORD=password
|
- POSTGRES_PASSWORD=password
|
||||||
|
@ -161,7 +161,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "8581:8581"
|
- "8581:8581"
|
||||||
postgres_epsilon:
|
postgres_epsilon:
|
||||||
image: postgres:12-alpine
|
image: postgres:14-alpine
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=lemmy
|
- POSTGRES_USER=lemmy
|
||||||
- POSTGRES_PASSWORD=password
|
- POSTGRES_PASSWORD=password
|
||||||
|
|
|
@ -2,7 +2,7 @@ version: '2.2'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12-alpine
|
image: postgres:14-alpine
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=lemmy
|
- POSTGRES_USER=lemmy
|
||||||
- POSTGRES_PASSWORD=password
|
- 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
|
export LEMMY_CONFIG_LOCATION=../../config/config.hjson
|
||||||
RUST_BACKTRACE=1 \
|
RUST_BACKTRACE=1 \
|
||||||
cargo test --workspace --no-fail-fast
|
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("/remove", web::post().to(route_post_crud::<RemoveComment>))
|
||||||
.route(
|
.route(
|
||||||
"/mark_as_read",
|
"/mark_as_read",
|
||||||
web::post().to(route_post::<MarkCommentAsRead>),
|
web::post().to(route_post::<MarkCommentReplyAsRead>),
|
||||||
)
|
)
|
||||||
.route("/like", web::post().to(route_post::<CreateCommentLike>))
|
.route("/like", web::post().to(route_post::<CreateCommentLike>))
|
||||||
.route("/save", web::put().to(route_post::<SaveComment>))
|
.route("/save", web::put().to(route_post::<SaveComment>))
|
||||||
|
|
Loading…
Reference in a new issue