Remove aggregate tables (fixes #4869) (#5407)

* migration

* update code

* tests

* triggers

* fix

* fmt

* clippy

* post aggregate migration

* changes for post aggregate code

* wip: update tests for post aggregate

* format

* fix partialeq

* trigger fix

* fix post insert trigger

* wip

* reorder

* fixes

* community aggregate migration

* update code

* triggers

* person aggregate migration

* person aggregate code

* person triggers

* test fixes

* fix scheduled task

* update api tests

* site_aggregates to local_site migration

* site_aggregates code changes

* triggers, tests

* more fixes

* Rename PersonPostAggregates to PostActions

* Merge local_user_vote_display_mode into local_user

* fix schema

* remove duplicate fields

* remove "aggregates" from index names

* uncomment indices

* if count = 0

* remove commentaggregates

* Fix triggers in remove aggregates tables pr (#5451)

* prevent all db_schema test errors

* fix the delete_comments_before_post problem in a way that doesn't affect the returned number of affected rows

* remove unnecessary recursion checks and add comment to remaining check

* clean up

* Fixing SQL format.

* Update triggers.sql

* Update triggers.sql

* Update triggers.sql

* Update triggers.sql

* remove update of deleted column

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>

* rename migration

* Fix migration errors

* Fixing person_saved_combined. (#5481)

---------

Co-authored-by: dullbananas <dull.bananas0@gmail.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
Nutomic 2025-03-06 15:18:41 +00:00 committed by GitHub
parent 7b86307760
commit 9a64902ace
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 2189 additions and 2397 deletions

View file

@ -29,7 +29,7 @@
"eslint": "^9.20.0",
"eslint-plugin-prettier": "^5.2.3",
"jest": "^29.5.0",
"lemmy-js-client": "1.0.0-block-nsfw.1",
"lemmy-js-client": "0.20.0-remove-aggregate-tables.5",
"prettier": "^3.5.0",
"ts-jest": "^29.1.0",
"tsoa": "^6.6.0",

View file

@ -33,8 +33,8 @@ importers:
specifier: ^29.5.0
version: 29.7.0(@types/node@22.13.1)
lemmy-js-client:
specifier: 1.0.0-block-nsfw.1
version: 1.0.0-block-nsfw.1
specifier: 0.20.0-remove-aggregate-tables.5
version: 0.20.0-remove-aggregate-tables.5
prettier:
specifier: ^3.5.0
version: 3.5.0
@ -1528,8 +1528,8 @@ packages:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
lemmy-js-client@1.0.0-block-nsfw.1:
resolution: {integrity: sha512-7dIGSflkfl6JZ57tNNwoI4xwHc3uMkj9mp3lMMUh+DkSnvUNEc1BCk4sBGLYJTXGr4XreeJT99bM67RO8fGkmA==}
lemmy-js-client@0.20.0-remove-aggregate-tables.5:
resolution: {integrity: sha512-A/p4LLWNiVp7fsQOctbFm/biBAunk0FIl5X79WJ/hRu/UiD1M+tCLGYPGdy308R+zscZsDNqECe1LYBenOFzOA==}
leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
@ -4169,7 +4169,7 @@ snapshots:
kleur@3.0.3: {}
lemmy-js-client@1.0.0-block-nsfw.1: {}
lemmy-js-client@0.20.0-remove-aggregate-tables.5: {}
leven@3.1.0: {}

View file

@ -81,19 +81,19 @@ test("Create a comment", async () => {
expect(commentRes.comment_view.comment.content).toBeDefined();
expect(commentRes.comment_view.community.local).toBe(false);
expect(commentRes.comment_view.creator.local).toBe(true);
expect(commentRes.comment_view.counts.score).toBe(1);
expect(commentRes.comment_view.comment.score).toBe(1);
// Make sure that comment is liked on beta
let betaComment = (
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
)
).comment;
expect(betaComment).toBeDefined();
expect(betaComment?.community.local).toBe(true);
expect(betaComment?.creator.local).toBe(false);
expect(betaComment?.counts.score).toBe(1);
expect(betaComment?.comment.score).toBe(1);
assertCommentFederation(betaComment, commentRes.comment_view);
});
@ -293,48 +293,48 @@ test("Unlike a comment", async () => {
let gammaComment1 = (
await waitUntil(
() => resolveComment(gamma, commentRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
)
).comment;
expect(gammaComment1).toBeDefined();
expect(gammaComment1?.community.local).toBe(false);
expect(gammaComment1?.creator.local).toBe(false);
expect(gammaComment1?.counts.score).toBe(1);
expect(gammaComment1?.comment.score).toBe(1);
let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
expect(unlike.comment_view.counts.score).toBe(0);
expect(unlike.comment_view.comment.score).toBe(0);
// Make sure that comment is unliked on beta
let betaComment = (
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment),
c => c.comment?.counts.score === 0,
c => c.comment?.comment.score === 0,
)
).comment;
expect(betaComment).toBeDefined();
expect(betaComment?.community.local).toBe(true);
expect(betaComment?.creator.local).toBe(false);
expect(betaComment?.counts.score).toBe(0);
expect(betaComment?.comment.score).toBe(0);
// Make sure that comment is unliked on gamma, downstream peer
// This is testing replication from remote-home-remote (alpha-beta-gamma)
let gammaComment = (
await waitUntil(
() => resolveComment(gamma, commentRes.comment_view.comment),
c => c.comment?.counts.score === 0,
c => c.comment?.comment.score === 0,
)
).comment;
expect(gammaComment).toBeDefined();
expect(gammaComment?.community.local).toBe(false);
expect(gammaComment?.creator.local).toBe(false);
expect(gammaComment?.counts.score).toBe(0);
expect(gammaComment?.comment.score).toBe(0);
});
test("Federated comment like", async () => {
let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
);
// Find the comment on beta
let betaComment = (
@ -346,14 +346,14 @@ test("Federated comment like", async () => {
}
let like = await likeComment(beta, 1, betaComment.comment);
expect(like.comment_view.counts.score).toBe(2);
expect(like.comment_view.comment.score).toBe(2);
// Get the post from alpha, check the likes
let postComments = await waitUntil(
() => getComments(alpha, postOnAlphaRes.post_view.post.id),
c => c.comments[0].counts.score === 2,
c => c.comments[0].comment.score === 2,
);
expect(postComments.comments[0].counts.score).toBe(2);
expect(postComments.comments[0].comment.score).toBe(2);
});
test("Reply to a comment from another instance, get notification", async () => {
@ -377,7 +377,7 @@ test("Reply to a comment from another instance, get notification", async () => {
let betaComment = (
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
)
).comment;
@ -397,12 +397,12 @@ test("Reply to a comment from another instance, get notification", async () => {
expect(getCommentParentId(replyRes.comment_view.comment)).toBe(
betaComment.comment.id,
);
expect(replyRes.comment_view.counts.score).toBe(1);
expect(replyRes.comment_view.comment.score).toBe(1);
// Make sure that reply comment is seen on alpha
let commentSearch = await waitUntil(
() => resolveComment(alpha, replyRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
);
let alphaComment = commentSearch.comment!;
let postComments = await waitUntil(
@ -418,7 +418,7 @@ test("Reply to a comment from another instance, get notification", async () => {
);
expect(alphaComment.community.local).toBe(false);
expect(alphaComment.creator.local).toBe(false);
expect(alphaComment.counts.score).toBe(1);
expect(alphaComment.comment.score).toBe(1);
assertCommentFederation(alphaComment, replyRes.comment_view);
// Did alpha get notified of the reply from beta?
@ -441,7 +441,7 @@ test("Reply to a comment from another instance, get notification", async () => {
expect(alphaReply.comment.content).toBeDefined();
expect(alphaReply.community.local).toBe(false);
expect(alphaReply.creator.local).toBe(false);
expect(alphaReply.counts.score).toBe(1);
expect(alphaReply.comment.score).toBe(1);
// ToDo: interesting alphaRepliesRes.replies[0].comment_reply.id is 1, meaning? how did that come about?
expect(alphaReply.comment.id).toBe(alphaComment.comment.id);
// this is a new notification, getReplies fetch was for read/unread both, confirm it is unread.
@ -515,7 +515,7 @@ test("Mention beta from alpha comment", async () => {
expect(mentionRes.comment_view.comment.content).toBeDefined();
expect(mentionRes.comment_view.community.local).toBe(false);
expect(mentionRes.comment_view.creator.local).toBe(true);
expect(mentionRes.comment_view.counts.score).toBe(1);
expect(mentionRes.comment_view.comment.score).toBe(1);
// get beta's localized copy of the alpha post
let betaPost = await waitForPost(beta, postOnAlphaRes.post_view.post);
@ -528,7 +528,7 @@ test("Mention beta from alpha comment", async () => {
// Make sure that both new comments are seen on beta and have parent/child relationship
let betaPostComments = await waitUntil(
() => getComments(beta, betaPost!.post.id),
c => c.comments[1]?.counts.score === 1,
c => c.comments[1]?.comment.score === 1,
);
expect(betaPostComments.comments.length).toEqual(2);
// the trunk-branch root comment will be older than the mention reply comment, so index 1
@ -542,7 +542,7 @@ test("Mention beta from alpha comment", async () => {
);
expect(betaRootComment.community.local).toBe(true);
expect(betaRootComment.creator.local).toBe(false);
expect(betaRootComment.counts.score).toBe(1);
expect(betaRootComment.comment.score).toBe(1);
assertCommentFederation(betaRootComment, commentRes.comment_view);
let mentionsRes = await waitUntil(
@ -554,7 +554,7 @@ test("Mention beta from alpha comment", async () => {
expect(firstMention.comment.content).toBeDefined();
expect(firstMention.community.local).toBe(true);
expect(firstMention.creator.local).toBe(false);
expect(firstMention.counts.score).toBe(1);
expect(firstMention.comment.score).toBe(1);
// the reply comment with mention should be the most fresh, newest, index 0
expect(firstMention.person_comment_mention.comment_id).toBe(
betaPostComments.comments[0].comment.id,
@ -606,17 +606,17 @@ test("A and G subscribe to B (center) A posts, G mentions B, it gets announced t
expect(commentRes.comment_view.comment.content).toBe(commentContent);
expect(commentRes.comment_view.community.local).toBe(false);
expect(commentRes.comment_view.creator.local).toBe(true);
expect(commentRes.comment_view.counts.score).toBe(1);
expect(commentRes.comment_view.comment.score).toBe(1);
// Make sure alpha sees it
let alphaPostComments2 = await waitUntil(
() => getComments(alpha, alphaPost.post_view.post.id),
e => e.comments[0]?.counts.score === 1,
e => e.comments[0]?.comment.score === 1,
);
expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
expect(alphaPostComments2.comments[0].community.local).toBe(true);
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
expect(alphaPostComments2.comments[0].comment.score).toBe(1);
assertCommentFederation(
alphaPostComments2.comments[0],
commentRes.comment_view,
@ -688,17 +688,17 @@ test("Check that activity from another instance is sent to third instance", asyn
expect(commentRes.comment_view.comment.content).toBe(commentContent);
expect(commentRes.comment_view.community.local).toBe(false);
expect(commentRes.comment_view.creator.local).toBe(true);
expect(commentRes.comment_view.counts.score).toBe(1);
expect(commentRes.comment_view.comment.score).toBe(1);
// Make sure alpha sees it
let alphaPostComments2 = await waitUntil(
() => getComments(alpha, alphaPost!.post.id),
e => e.comments[0]?.counts.score === 1,
e => e.comments[0]?.comment.score === 1,
);
expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
expect(alphaPostComments2.comments[0].community.local).toBe(false);
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
expect(alphaPostComments2.comments[0].comment.score).toBe(1);
assertCommentFederation(
alphaPostComments2.comments[0],
commentRes.comment_view,

View file

@ -396,7 +396,7 @@ test.skip("Community follower count is federated", async () => {
).community;
// Make sure there is 1 subscriber
expect(followed?.counts.subscribers).toBe(1);
expect(followed?.community.subscribers).toBe(1);
// Follow the community from gamma
resolved = await resolveCommunity(gamma, communityActorId);
@ -413,7 +413,7 @@ test.skip("Community follower count is federated", async () => {
).community;
// Make sure there are 2 subscribers
expect(followed?.counts?.subscribers).toBe(2);
expect(followed?.community?.subscribers).toBe(2);
// Follow the community from delta
resolved = await resolveCommunity(delta, communityActorId);
@ -430,13 +430,13 @@ test.skip("Community follower count is federated", async () => {
).community;
// Make sure there are 3 subscribers
expect(followed?.counts?.subscribers).toBe(3);
expect(followed?.community?.subscribers).toBe(3);
});
test("Dont receive community activities after unsubscribe", async () => {
let communityRes = await createCommunity(alpha);
expect(communityRes.community_view.community.name).toBeDefined();
expect(communityRes.community_view.counts.subscribers).toBe(1);
expect(communityRes.community_view.community.subscribers).toBe(1);
let betaCommunity = (
await resolveCommunity(beta, communityRes.community_view.community.ap_id)
@ -451,7 +451,7 @@ test("Dont receive community activities after unsubscribe", async () => {
alpha,
communityRes.community_view.community.id,
);
expect(communityRes1.community_view.counts.subscribers).toBe(2);
expect(communityRes1.community_view.community.subscribers).toBe(2);
// temporarily block alpha, so that it doesn't know about unfollow
var allow_instance_params: AdminAllowInstanceParams = {
@ -470,7 +470,7 @@ test("Dont receive community activities after unsubscribe", async () => {
alpha,
communityRes.community_view.community.id,
);
expect(communityRes2.community_view.counts.subscribers).toBe(2);
expect(communityRes2.community_view.community.subscribers).toBe(2);
// unblock alpha
allow_instance_params.allow = true;
@ -492,7 +492,7 @@ test("Dont receive community activities after unsubscribe", async () => {
test("Fetch community, includes posts", async () => {
let communityRes = await createCommunity(alpha);
expect(communityRes.community_view.community.name).toBeDefined();
expect(communityRes.community_view.counts.subscribers).toBe(1);
expect(communityRes.community_view.community.subscribers).toBe(1);
let postRes = await createPost(
alpha,

View file

@ -27,21 +27,21 @@ test("Follow local community", async () => {
// Make sure the follow response went through
expect(follow.community_view.community.local).toBe(true);
expect(follow.community_view.subscribed).toBe("Subscribed");
expect(follow.community_view.counts.subscribers).toBe(
community.counts.subscribers + 1,
expect(follow.community_view.community.subscribers).toBe(
community.community.subscribers + 1,
);
expect(follow.community_view.counts.subscribers_local).toBe(
community.counts.subscribers_local + 1,
expect(follow.community_view.community.subscribers_local).toBe(
community.community.subscribers_local + 1,
);
// Test an unfollow
let unfollow = await followCommunity(user, false, community.community.id);
expect(unfollow.community_view.subscribed).toBe("NotSubscribed");
expect(unfollow.community_view.counts.subscribers).toBe(
community.counts.subscribers,
expect(unfollow.community_view.community.subscribers).toBe(
community.community.subscribers,
);
expect(unfollow.community_view.counts.subscribers_local).toBe(
community.counts.subscribers_local,
expect(unfollow.community_view.community.subscribers_local).toBe(
community.community.subscribers_local,
);
});
@ -51,7 +51,7 @@ test("Follow federated community", async () => {
const betaCommunityInitial = (
await waitUntil(
() => resolveBetaCommunity(alpha),
c => !!c.community && c.community?.counts.subscribers >= 1,
c => !!c.community && c.community?.community.subscribers >= 1,
)
).community;
if (!betaCommunityInitial) {
@ -74,14 +74,14 @@ test("Follow federated community", async () => {
expect(betaCommunity?.community.local).toBe(false);
expect(betaCommunity?.community.name).toBe("main");
expect(betaCommunity?.subscribed).toBe("Subscribed");
expect(betaCommunity?.counts.subscribers_local).toBe(
betaCommunityInitial.counts.subscribers_local + 1,
expect(betaCommunity?.community.subscribers_local).toBe(
betaCommunityInitial.community.subscribers_local + 1,
);
// check that unfollow was federated
let communityOnBeta1 = await resolveBetaCommunity(beta);
expect(communityOnBeta1.community?.counts.subscribers).toBe(
betaCommunityInitial.counts.subscribers + 1,
expect(communityOnBeta1.community?.community.subscribers).toBe(
betaCommunityInitial.community.subscribers + 1,
);
// Check it from local
@ -113,11 +113,11 @@ test("Follow federated community", async () => {
let communityOnBeta2 = await waitUntil(
() => resolveBetaCommunity(beta),
c =>
c.community?.counts.subscribers ===
betaCommunityInitial.counts.subscribers,
c.community?.community.subscribers ===
betaCommunityInitial.community.subscribers,
);
expect(communityOnBeta2.community?.counts.subscribers).toBe(
betaCommunityInitial.counts.subscribers,
expect(communityOnBeta2.community?.community.subscribers).toBe(
betaCommunityInitial.community.subscribers,
);
expect(communityOnBeta2.community?.counts.subscribers_local).toBe(1);
expect(communityOnBeta2.community?.community.subscribers_local).toBe(1);
});

View file

@ -125,19 +125,19 @@ test("Create a post", async () => {
expect(postRes.post_view.post).toBeDefined();
expect(postRes.post_view.community.local).toBe(false);
expect(postRes.post_view.creator.local).toBe(true);
expect(postRes.post_view.counts.score).toBe(1);
expect(postRes.post_view.post.score).toBe(1);
// Make sure that post is liked on beta
const betaPost = await waitForPost(
beta,
postRes.post_view.post,
res => res?.counts.score === 1,
res => res?.post.score === 1,
);
expect(betaPost).toBeDefined();
expect(betaPost?.community.local).toBe(true);
expect(betaPost?.creator.local).toBe(false);
expect(betaPost?.counts.score).toBe(1);
expect(betaPost?.post.score).toBe(1);
await assertPostFederation(betaPost, postRes.post_view);
// Delta only follows beta, so it should not see an alpha ap_id
@ -165,23 +165,23 @@ test("Unlike a post", async () => {
}
let postRes = await createPost(alpha, betaCommunity.community.id);
let unlike = await likePost(alpha, 0, postRes.post_view.post);
expect(unlike.post_view.counts.score).toBe(0);
expect(unlike.post_view.post.score).toBe(0);
// Try to unlike it again, make sure it stays at 0
let unlike2 = await likePost(alpha, 0, postRes.post_view.post);
expect(unlike2.post_view.counts.score).toBe(0);
expect(unlike2.post_view.post.score).toBe(0);
// Make sure that post is unliked on beta
const betaPost = await waitForPost(
beta,
postRes.post_view.post,
post => post?.counts.score === 0,
post => post?.post.score === 0,
);
expect(betaPost).toBeDefined();
expect(betaPost?.community.local).toBe(true);
expect(betaPost?.creator.local).toBe(false);
expect(betaPost?.counts.score).toBe(0);
expect(betaPost?.post.score).toBe(0);
await assertPostFederation(betaPost, postRes.post_view);
});
@ -665,7 +665,7 @@ test("Enforce community ban for federated user", async () => {
expect(postRes3.post_view.post).toBeDefined();
expect(postRes3.post_view.community.local).toBe(false);
expect(postRes3.post_view.creator.local).toBe(true);
expect(postRes3.post_view.counts.score).toBe(1);
expect(postRes3.post_view.post.score).toBe(1);
// Make sure that post makes it to beta community
let postRes4 = await waitForPost(beta, postRes3.post_view.post);
@ -806,7 +806,7 @@ test("Fetch post via redirect", async () => {
const betaPost = await waitForPost(
beta,
alphaPost.post_view.post,
res => res?.counts.score === 1,
res => res?.post.score === 1,
);
expect(betaPost).toBeDefined();
@ -879,7 +879,7 @@ test("Mention beta from alpha post body", async () => {
expect(postOnAlphaRes.post_view.post.body).toBeDefined();
expect(postOnAlphaRes.post_view.community.local).toBe(false);
expect(postOnAlphaRes.post_view.creator.local).toBe(true);
expect(postOnAlphaRes.post_view.counts.score).toBe(1);
expect(postOnAlphaRes.post_view.post.score).toBe(1);
// get beta's localized copy of the alpha post
let betaPost = await waitForPost(beta, postOnAlphaRes.post_view.post);
@ -899,7 +899,7 @@ test("Mention beta from alpha post body", async () => {
expect(firstMention.post.body).toBeDefined();
expect(firstMention.community.local).toBe(true);
expect(firstMention.creator.local).toBe(false);
expect(firstMention.counts.score).toBe(1);
expect(firstMention.post.score).toBe(1);
expect(firstMention.person_post_mention.post_id).toBe(betaPost.post.id);
});

View file

@ -10,7 +10,6 @@ use lemmy_db_schema::{
source::{
actor_language::LocalUserLanguage,
local_user::{LocalUser, LocalUserUpdateForm},
local_user_vote_display_mode::{LocalUserVoteDisplayMode, LocalUserVoteDisplayModeUpdateForm},
person::{Person, PersonUpdateForm},
},
traits::Crud,
@ -132,20 +131,15 @@ pub async fn save_user_settings(
collapse_bot_comments: data.collapse_bot_comments,
auto_mark_fetched_posts_as_read: data.auto_mark_fetched_posts_as_read,
hide_media: data.hide_media,
// Update the vote display modes
show_score: data.show_scores,
show_upvotes: data.show_upvotes,
show_downvotes: data.show_downvotes,
show_upvote_percentage: data.show_upvote_percentage,
..Default::default()
};
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await?;
// Update the vote display modes
let vote_display_modes_form = LocalUserVoteDisplayModeUpdateForm {
score: data.show_scores,
upvotes: data.show_upvotes,
downvotes: data.show_downvotes,
upvote_percentage: data.show_upvote_percentage,
};
LocalUserVoteDisplayMode::update(&mut context.pool(), local_user_id, &vote_display_modes_form)
.await?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -13,7 +13,6 @@ use actix_web_httpauth::headers::authorization::{Authorization, Bearer};
use chrono::{DateTime, Days, Local, TimeZone, Utc};
use enum_map::{enum_map, EnumMap};
use lemmy_db_schema::{
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
newtypes::{CommentId, CommunityId, DbUrl, InstanceId, PersonId, PostId, PostOrCommentId},
source::{
comment::{Comment, CommentLike, CommentUpdateForm},
@ -38,6 +37,7 @@ use lemmy_db_schema::{
person::{Person, PersonUpdateForm},
person_block::PersonBlock,
post::{Post, PostLike},
post_actions::{PostActions, PostActionsForm},
private_message::PrivateMessage,
registration_application::RegistrationApplication,
site::Site,
@ -158,13 +158,13 @@ pub async fn update_read_comments(
read_comments: i64,
pool: &mut DbPool<'_>,
) -> LemmyResult<()> {
let person_post_agg_form = PersonPostAggregatesForm {
let person_post_agg_form = PostActionsForm {
person_id,
post_id,
read_comments,
};
PersonPostAggregates::upsert(pool, &person_post_agg_form).await?;
PostActions::upsert(pool, &person_post_agg_form).await?;
Ok(())
}

View file

@ -142,7 +142,7 @@ pub async fn create_comment(
update_read_comments(
local_user_view.person.id,
post_id,
post_view.counts.comments + 1,
post.comments + 1,
&mut context.pool(),
)
.await?;

View file

@ -69,7 +69,7 @@ pub async fn get_post(
update_read_comments(
person_id,
post_id,
post_view.counts.comments,
post_view.post.comments,
&mut context.pool(),
)
.await?;

View file

@ -28,7 +28,6 @@ use lemmy_api_common::{
utils::{check_post_deleted_or_removed, is_mod_or_admin},
};
use lemmy_db_schema::{
aggregates::structs::CommentAggregates,
newtypes::{PersonId, PostOrCommentId},
source::{
activity::ActivitySendTargets,
@ -160,7 +159,7 @@ impl ActivityHandler for CreateOrUpdateNote {
CommentLike::like(&mut context.pool(), &like_form).await?;
// Calculate initial hot_rank
CommentAggregates::update_hot_rank(&mut context.pool(), comment.id).await?;
Comment::update_hot_rank(&mut context.pool(), comment.id).await?;
let do_send_email = self.kind == CreateOrUpdateType::Create;
let actor = self.actor.dereference(context).await?;

View file

@ -22,7 +22,6 @@ use activitypub_federation::{
};
use lemmy_api_common::{build_response::send_local_notifs, context::LemmyContext};
use lemmy_db_schema::{
aggregates::structs::PostAggregates,
newtypes::{PersonId, PostOrCommentId},
source::{
activity::ActivitySendTargets,
@ -121,7 +120,7 @@ impl ActivityHandler for CreateOrUpdatePage {
PostLike::like(&mut context.pool(), &like_form).await?;
// Calculate initial hot_rank for post
PostAggregates::update_ranks(&mut context.pool(), post.id).await?;
Post::update_ranks(&mut context.pool(), post.id).await?;
let do_send_email = self.kind == CreateOrUpdateType::Create;
let actor = self.actor.dereference(context).await?;

View file

@ -18,7 +18,6 @@ use lemmy_db_schema::{
instance::Instance,
instance_block::{InstanceBlock, InstanceBlockForm},
local_user::{LocalUser, LocalUserUpdateForm},
local_user_vote_display_mode::{LocalUserVoteDisplayMode, LocalUserVoteDisplayModeUpdateForm},
person::{Person, PersonUpdateForm},
person_block::{PersonBlock, PersonBlockForm},
post::{PostSaved, PostSavedForm},
@ -55,7 +54,6 @@ pub struct UserSettingsBackup {
// TODO: might be worth making a separate struct for settings backup, to avoid breakage in case
// fields are renamed, and to avoid storing unnecessary fields like person_id or email
pub settings: Option<LocalUser>,
pub vote_display_mode_settings: Option<LocalUserVoteDisplayMode>,
#[serde(default)]
pub followed_communities: Vec<ObjectId<ApubCommunity>>,
#[serde(default)]
@ -85,7 +83,6 @@ pub async fn export_settings(
matrix_id: local_user_view.person.matrix_user_id,
bot_account: local_user_view.person.bot_account.into(),
settings: Some(local_user_view.local_user),
vote_display_mode_settings: Some(local_user_view.local_user_vote_display_mode),
followed_communities: vec_into(lists.followed_communities),
blocked_communities: vec_into(lists.blocked_communities),
blocked_instances: lists.blocked_instances,
@ -130,6 +127,10 @@ pub async fn import_settings(
blur_nsfw: data.settings.as_ref().map(|s| s.blur_nsfw),
infinite_scroll_enabled: data.settings.as_ref().map(|s| s.infinite_scroll_enabled),
post_listing_mode: data.settings.as_ref().map(|s| s.post_listing_mode),
show_score: data.settings.as_ref().map(|s| s.show_score),
show_upvotes: data.settings.as_ref().map(|s| s.show_upvotes),
show_downvotes: data.settings.as_ref().map(|s| s.show_downvotes),
show_upvote_percentage: data.settings.as_ref().map(|s| s.show_upvote_percentage),
..Default::default()
};
LocalUser::update(
@ -139,27 +140,6 @@ pub async fn import_settings(
)
.await?;
// Update the vote display mode settings
let vote_display_mode_form = LocalUserVoteDisplayModeUpdateForm {
score: data.vote_display_mode_settings.as_ref().map(|s| s.score),
upvotes: data.vote_display_mode_settings.as_ref().map(|s| s.upvotes),
downvotes: data
.vote_display_mode_settings
.as_ref()
.map(|s| s.downvotes),
upvote_percentage: data
.vote_display_mode_settings
.as_ref()
.map(|s| s.upvote_percentage),
};
LocalUserVoteDisplayMode::update(
&mut context.pool(),
local_user_view.local_user.id,
&vote_display_mode_form,
)
.await?;
let url_count = data.followed_communities.len()
+ data.blocked_communities.len()
+ data.blocked_users.len()

View file

@ -9,7 +9,7 @@ use activitypub_federation::{
traits::Collection,
};
use lemmy_api_common::{context::LemmyContext, utils::generate_followers_url};
use lemmy_db_schema::aggregates::structs::CommunityAggregates;
use lemmy_db_schema::source::community::Community;
use lemmy_db_views::structs::CommunityFollowerView;
use lemmy_utils::error::LemmyError;
use url::Url;
@ -54,12 +54,8 @@ impl Collection for ApubCommunityFollower {
community: &Self::Owner,
context: &Data<Self::DataType>,
) -> Result<Self, Self::Error> {
CommunityAggregates::update_federated_followers(
&mut context.pool(),
community.id,
json.total_items,
)
.await?;
Community::update_federated_followers(&mut context.pool(), community.id, json.total_items)
.await?;
Ok(ApubCommunityFollower(()))
}

View file

@ -6,7 +6,7 @@
-- before the automatic deletion of the row that references it. This is not a problem for insert or delete.
--
-- Triggers that update multiple tables should use this order: person_aggregates, comment_aggregates,
-- post_aggregates, community_aggregates, site_aggregates
-- post, community_aggregates, site_aggregates
-- * The order matters because the updated rows are locked until the end of the transaction, and statements
-- in a trigger don't use separate transactions. This means that updates closer to the beginning cause
-- longer locks because the duration of each update extends the durations of the locks caused by previous
@ -19,19 +19,6 @@
--
--
-- Create triggers for both post and comments
CREATE FUNCTION r.creator_id_from_post_aggregates (agg post_aggregates)
RETURNS int IMMUTABLE PARALLEL SAFE RETURN agg.creator_id;
CREATE FUNCTION r.creator_id_from_comment_aggregates (agg comment_aggregates)
RETURNS int IMMUTABLE PARALLEL SAFE RETURN (
SELECT
creator_id
FROM
comment
WHERE
comment.id = agg.comment_id LIMIT 1
);
CREATE PROCEDURE r.post_or_comment (table_name text)
LANGUAGE plpgsql
AS $a$
@ -41,7 +28,7 @@ BEGIN
CALL r.create_triggers ('thing_actions', $$
BEGIN
WITH thing_diff AS ( UPDATE
thing_aggregates AS a
thing AS a
SET
score = a.score + diff.upvotes - diff.downvotes, upvotes = a.upvotes + diff.upvotes, downvotes = a.downvotes + diff.downvotes, controversy_rank = r.controversy_rank ((a.upvotes + diff.upvotes)::numeric, (a.downvotes + diff.downvotes)::numeric)
FROM (
@ -49,18 +36,18 @@ BEGIN
(thing_actions).thing_id, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score = 1), 0) AS upvotes, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score != 1), 0) AS downvotes FROM select_old_and_new_rows AS old_and_new_rows
WHERE (thing_actions).like_score IS NOT NULL GROUP BY (thing_actions).thing_id) AS diff
WHERE
a.thing_id = diff.thing_id
a.id = diff.thing_id
AND (diff.upvotes, diff.downvotes) != (0, 0)
RETURNING
r.creator_id_from_thing_aggregates (a.*) AS creator_id, diff.upvotes - diff.downvotes AS score)
a.creator_id AS creator_id, diff.upvotes - diff.downvotes AS score)
UPDATE
person_aggregates AS a
person AS a
SET
thing_score = a.thing_score + diff.score FROM (
SELECT
creator_id, sum(score) AS score FROM thing_diff GROUP BY creator_id) AS diff
WHERE
a.person_id = diff.creator_id
a.id = diff.creator_id
AND diff.score != 0;
RETURN NULL;
END;
@ -93,23 +80,35 @@ END;
CALL r.create_triggers ('comment', $$
BEGIN
UPDATE
person_aggregates AS a
SET
comment_count = a.comment_count + diff.comment_count
FROM (
-- Prevent infinite recursion
IF (
SELECT
(comment).creator_id, coalesce(sum(count_diff), 0) AS comment_count
FROM select_old_and_new_rows AS old_and_new_rows
WHERE
r.is_counted (comment)
GROUP BY (comment).creator_id) AS diff
WHERE
a.person_id = diff.creator_id
AND diff.comment_count != 0;
count(*)
FROM select_old_and_new_rows AS old_and_new_rows) = 0 THEN
RETURN NULL;
END IF;
UPDATE
comment_aggregates AS a
person AS a
SET
comment_count = a.comment_count + diff.comment_count
FROM (
SELECT
(comment).creator_id,
coalesce(sum(count_diff), 0) AS comment_count
FROM
select_old_and_new_rows AS old_and_new_rows
WHERE
r.is_counted (comment)
GROUP BY
(comment).creator_id) AS diff
WHERE
a.id = diff.creator_id
AND diff.comment_count != 0;
UPDATE
comment AS a
SET
child_count = a.child_count + diff.child_count
FROM (
@ -143,66 +142,43 @@ FROM (
GROUP BY
parent_id) AS diff
WHERE
a.comment_id = diff.parent_id
a.id = diff.parent_id
AND diff.child_count != 0;
WITH post_diff AS (
UPDATE
post_aggregates AS a
SET
comments = a.comments + diff.comments,
newest_comment_time = GREATEST (a.newest_comment_time, diff.newest_comment_time),
newest_comment_time_necro = GREATEST (a.newest_comment_time_necro, diff.newest_comment_time_necro)
FROM (
SELECT
post.id AS post_id,
coalesce(sum(count_diff), 0) AS comments,
-- Old rows are excluded using `count_diff = 1`
max((comment).published) FILTER (WHERE count_diff = 1) AS newest_comment_time,
max((comment).published) FILTER (WHERE count_diff = 1
-- Ignore comments from the post's creator
AND post.creator_id != (comment).creator_id
-- Ignore comments on old posts
AND post.published > ((comment).published - '2 days'::interval)) AS newest_comment_time_necro,
r.is_counted (post.*) AS include_in_community_aggregates
FROM
select_old_and_new_rows AS old_and_new_rows
LEFT JOIN post ON post.id = (comment).post_id
WHERE
r.is_counted (comment)
GROUP BY
post.id) AS diff
WHERE
a.post_id = diff.post_id
AND (diff.comments,
GREATEST (a.newest_comment_time, diff.newest_comment_time),
GREATEST (a.newest_comment_time_necro, diff.newest_comment_time_necro)) != (0,
a.newest_comment_time,
a.newest_comment_time_necro)
RETURNING
a.community_id,
diff.comments,
diff.include_in_community_aggregates)
UPDATE
community_aggregates AS a
post AS a
SET
comments = a.comments + diff.comments
comments = a.comments + diff.comments,
newest_comment_time = GREATEST (a.newest_comment_time, diff.newest_comment_time),
newest_comment_time_necro = GREATEST (a.newest_comment_time_necro, diff.newest_comment_time_necro)
FROM (
SELECT
community_id,
sum(comments) AS comments
FROM
post_diff
WHERE
post_diff.include_in_community_aggregates
GROUP BY
community_id) AS diff
post.id AS post_id,
coalesce(sum(count_diff), 0) AS comments,
-- Old rows are excluded using `count_diff = 1`
max((comment).published) FILTER (WHERE count_diff = 1) AS newest_comment_time,
max((comment).published) FILTER (WHERE count_diff = 1
-- Ignore comments from the post's creator
AND post.creator_id != (comment).creator_id
-- Ignore comments on old posts
AND post.published > ((comment).published - '2 days'::interval)) AS newest_comment_time_necro
FROM
select_old_and_new_rows AS old_and_new_rows
LEFT JOIN post ON post.id = (comment).post_id
WHERE
a.community_id = diff.community_id
AND diff.comments != 0;
r.is_counted (comment)
GROUP BY
post.id) AS diff
WHERE
a.id = diff.post_id
AND (diff.comments,
GREATEST (a.newest_comment_time, diff.newest_comment_time),
GREATEST (a.newest_comment_time_necro, diff.newest_comment_time_necro)) != (0,
a.newest_comment_time,
a.newest_comment_time_necro);
UPDATE
site_aggregates AS a
local_site AS a
SET
comments = a.comments + diff.comments
FROM (
@ -225,7 +201,7 @@ $$);
CALL r.create_triggers ('post', $$
BEGIN
UPDATE
person_aggregates AS a
person AS a
SET
post_count = a.post_count + diff.post_count
FROM (
@ -236,17 +212,19 @@ BEGIN
r.is_counted (post)
GROUP BY (post).creator_id) AS diff
WHERE
a.person_id = diff.creator_id
a.id = diff.creator_id
AND diff.post_count != 0;
UPDATE
community_aggregates AS a
community AS a
SET
posts = a.posts + diff.posts
posts = a.posts + diff.posts,
comments = a.comments + diff.comments
FROM (
SELECT
(post).community_id,
coalesce(sum(count_diff), 0) AS posts
coalesce(sum(count_diff), 0) AS posts,
coalesce(sum(count_diff * (post).comments), 0) AS comments
FROM
select_old_and_new_rows AS old_and_new_rows
WHERE
@ -254,11 +232,13 @@ FROM (
GROUP BY
(post).community_id) AS diff
WHERE
a.community_id = diff.community_id
AND diff.posts != 0;
a.id = diff.community_id
AND (diff.posts,
diff.comments) != (0,
0);
UPDATE
site_aggregates AS a
local_site AS a
SET
posts = a.posts + diff.posts
FROM (
@ -281,7 +261,7 @@ $$);
CALL r.create_triggers ('community', $$
BEGIN
UPDATE
site_aggregates AS a
local_site AS a
SET
communities = a.communities + diff.communities
FROM (
@ -303,7 +283,7 @@ $$);
CALL r.create_triggers ('person', $$
BEGIN
UPDATE
site_aggregates AS a
local_site AS a
SET
users = a.users + diff.users
FROM (
@ -320,51 +300,13 @@ END;
$$);
-- For community_aggregates.comments, don't include comments of deleted or removed posts
CREATE FUNCTION r.update_comment_count_from_post ()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE
community_aggregates AS a
SET
comments = a.comments + diff.comments
FROM (
SELECT
old_post.community_id,
sum((
CASE WHEN r.is_counted (new_post.*) THEN
1
ELSE
-1
END) * post_aggregates.comments) AS comments
FROM
new_post
INNER JOIN old_post ON new_post.id = old_post.id
AND (r.is_counted (new_post.*) != r.is_counted (old_post.*))
INNER JOIN post_aggregates ON post_aggregates.post_id = new_post.id
GROUP BY
old_post.community_id) AS diff
WHERE
a.community_id = diff.community_id
AND diff.comments != 0;
RETURN NULL;
END;
$$;
CREATE TRIGGER comment_count
AFTER UPDATE ON post REFERENCING OLD TABLE AS old_post NEW TABLE AS new_post
FOR EACH STATEMENT
EXECUTE FUNCTION r.update_comment_count_from_post ();
-- Count subscribers for communities.
-- subscribers should be updated only when a local community is followed by a local or remote person.
-- subscribers_local should be updated only when a local person follows a local or remote community.
CALL r.create_triggers ('community_actions', $$
BEGIN
UPDATE
community_aggregates AS a
community AS a
SET
subscribers = a.subscribers + diff.subscribers, subscribers_local = a.subscribers_local + diff.subscribers_local
FROM (
@ -375,7 +317,7 @@ BEGIN
LEFT JOIN person ON person.id = (community_actions).person_id
WHERE (community_actions).followed IS NOT NULL GROUP BY (community_actions).community_id) AS diff
WHERE
a.community_id = diff.community_id
a.id = diff.community_id
AND (diff.subscribers, diff.subscribers_local) != (0, 0);
RETURN NULL;
@ -387,7 +329,7 @@ $$);
CALL r.create_triggers ('post_report', $$
BEGIN
UPDATE
post_aggregates AS a
post AS a
SET
report_count = a.report_count + diff.report_count, unresolved_report_count = a.unresolved_report_count + diff.unresolved_report_count
FROM (
@ -396,7 +338,7 @@ BEGIN
AND NOT (post_report).violates_instance_rules), 0) AS unresolved_report_count
FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (post_report).post_id) AS diff
WHERE (diff.report_count, diff.unresolved_report_count) != (0, 0)
AND a.post_id = diff.post_id;
AND a.id = diff.post_id;
RETURN NULL;
@ -407,7 +349,7 @@ $$);
CALL r.create_triggers ('comment_report', $$
BEGIN
UPDATE
comment_aggregates AS a
comment AS a
SET
report_count = a.report_count + diff.report_count, unresolved_report_count = a.unresolved_report_count + diff.unresolved_report_count
FROM (
@ -416,7 +358,7 @@ BEGIN
AND NOT (comment_report).violates_instance_rules), 0) AS unresolved_report_count
FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (comment_report).comment_id) AS diff
WHERE (diff.report_count, diff.unresolved_report_count) != (0, 0)
AND a.comment_id = diff.comment_id;
AND a.id = diff.comment_id;
RETURN NULL;
@ -427,7 +369,7 @@ $$);
CALL r.create_triggers ('community_report', $$
BEGIN
UPDATE
community_aggregates AS a
community AS a
SET
report_count = a.report_count + diff.report_count, unresolved_report_count = a.unresolved_report_count + diff.unresolved_report_count
FROM (
@ -435,7 +377,7 @@ BEGIN
(community_report).community_id, coalesce(sum(count_diff), 0) AS report_count, coalesce(sum(count_diff) FILTER (WHERE NOT (community_report).resolved), 0) AS unresolved_report_count
FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (community_report).community_id) AS diff
WHERE (diff.report_count, diff.unresolved_report_count) != (0, 0)
AND a.community_id = diff.community_id;
AND a.id = diff.community_id;
RETURN NULL;
@ -443,160 +385,7 @@ END;
$$);
-- These triggers create and update rows in each aggregates table to match its associated table's rows.
-- Deleting rows and updating IDs are already handled by `CASCADE` in foreign key constraints.
CREATE FUNCTION r.comment_aggregates_from_comment ()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO comment_aggregates (comment_id, published)
SELECT
id,
published
FROM
new_comment;
RETURN NULL;
END;
$$;
CREATE TRIGGER aggregates
AFTER INSERT ON comment REFERENCING NEW TABLE AS new_comment
FOR EACH STATEMENT
EXECUTE FUNCTION r.comment_aggregates_from_comment ();
CREATE FUNCTION r.community_aggregates_from_community ()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO community_aggregates (community_id, published)
SELECT
id,
published
FROM
new_community;
RETURN NULL;
END;
$$;
CREATE TRIGGER aggregates
AFTER INSERT ON community REFERENCING NEW TABLE AS new_community
FOR EACH STATEMENT
EXECUTE FUNCTION r.community_aggregates_from_community ();
CREATE FUNCTION r.person_aggregates_from_person ()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO person_aggregates (person_id)
SELECT
id
FROM
new_person;
RETURN NULL;
END;
$$;
CREATE TRIGGER aggregates
AFTER INSERT ON person REFERENCING NEW TABLE AS new_person
FOR EACH STATEMENT
EXECUTE FUNCTION r.person_aggregates_from_person ();
CREATE FUNCTION r.post_aggregates_from_post ()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO post_aggregates (post_id, published, newest_comment_time, newest_comment_time_necro, community_id, creator_id, instance_id, featured_community, featured_local)
SELECT
new_post.id,
new_post.published,
new_post.published,
new_post.published,
new_post.community_id,
new_post.creator_id,
community.instance_id,
new_post.featured_community,
new_post.featured_local
FROM
new_post
INNER JOIN community ON community.id = new_post.community_id;
RETURN NULL;
END;
$$;
CREATE TRIGGER aggregates
AFTER INSERT ON post REFERENCING NEW TABLE AS new_post
FOR EACH STATEMENT
EXECUTE FUNCTION r.post_aggregates_from_post ();
CREATE FUNCTION r.post_aggregates_from_post_update ()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE
post_aggregates
SET
featured_community = new_post.featured_community,
featured_local = new_post.featured_local
FROM
new_post
INNER JOIN old_post ON old_post.id = new_post.id
AND (old_post.featured_community,
old_post.featured_local) != (new_post.featured_community,
new_post.featured_local)
WHERE
post_aggregates.post_id = new_post.id;
RETURN NULL;
END;
$$;
CREATE TRIGGER aggregates_update
AFTER UPDATE ON post REFERENCING OLD TABLE AS old_post NEW TABLE AS new_post
FOR EACH STATEMENT
EXECUTE FUNCTION r.post_aggregates_from_post_update ();
CREATE FUNCTION r.site_aggregates_from_site ()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
-- only 1 row can be in site_aggregates because of the index idx_site_aggregates_1_row_only.
-- we only ever want to have a single value in site_aggregate because the site_aggregate triggers update all rows in that table.
-- a cleaner check would be to insert it for the local_site but that would break assumptions at least in the tests
INSERT INTO site_aggregates (site_id)
VALUES (NEW.id)
ON CONFLICT ((TRUE))
DO NOTHING;
RETURN NULL;
END;
$$;
CREATE TRIGGER aggregates
AFTER INSERT ON site
FOR EACH ROW
EXECUTE FUNCTION r.site_aggregates_from_site ();
-- Change the order of some cascading deletions to make deletion triggers run before the deletion of rows that the triggers need to read
CREATE FUNCTION r.delete_comments_before_post ()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
DELETE FROM comment AS c
WHERE c.post_id = OLD.id;
RETURN OLD;
END;
$$;
CREATE TRIGGER delete_comments
BEFORE DELETE ON post
FOR EACH ROW
EXECUTE FUNCTION r.delete_comments_before_post ();
CREATE FUNCTION r.delete_follow_before_person ()
RETURNS TRIGGER
LANGUAGE plpgsql
@ -647,6 +436,16 @@ BEGIN
IF NEW.local THEN
NEW.ap_id = coalesce(NEW.ap_id, r.local_url ('/post/' || NEW.id::text));
END IF;
-- Set aggregates
NEW.newest_comment_time = NEW.published;
NEW.newest_comment_time_necro = NEW.published;
NEW.instance_id = (
SELECT
community.instance_id
FROM
community
WHERE
community.id = NEW.community_id);
RETURN NEW;
END
$$;
@ -963,7 +762,7 @@ CALL r.create_search_combined_trigger ('community');
CALL r.create_search_combined_trigger ('person');
-- You also need to triggers to update the `score` column.
-- post | post_aggregates::score
-- post | post::score
-- comment | comment_aggregates::score
-- community | community_aggregates::users_active_monthly
-- person | person_aggregates::post_score
@ -979,13 +778,13 @@ BEGIN
SET
score = NEW.score
WHERE
post_id = NEW.post_id;
post_id = NEW.id;
RETURN NULL;
END
$$;
CREATE TRIGGER search_combined_post_score
AFTER UPDATE OF score ON post_aggregates
AFTER UPDATE OF score ON post
FOR EACH ROW
EXECUTE FUNCTION r.search_combined_post_score_update ();
@ -1000,13 +799,13 @@ BEGIN
SET
score = NEW.score
WHERE
comment_id = NEW.comment_id;
comment_id = NEW.id;
RETURN NULL;
END
$$;
CREATE TRIGGER search_combined_comment_score
AFTER UPDATE OF score ON comment_aggregates
AFTER UPDATE OF score ON comment
FOR EACH ROW
EXECUTE FUNCTION r.search_combined_comment_score_update ();
@ -1021,13 +820,13 @@ BEGIN
SET
score = NEW.post_score
WHERE
person_id = NEW.person_id;
person_id = NEW.id;
RETURN NULL;
END
$$;
CREATE TRIGGER search_combined_person_score
AFTER UPDATE OF post_score ON person_aggregates
AFTER UPDATE OF post_score ON person
FOR EACH ROW
EXECUTE FUNCTION r.search_combined_person_score_update ();
@ -1042,13 +841,13 @@ BEGIN
SET
score = NEW.users_active_month
WHERE
community_id = NEW.community_id;
community_id = NEW.id;
RETURN NULL;
END
$$;
CREATE TRIGGER search_combined_community_score
AFTER UPDATE OF users_active_month ON community_aggregates
AFTER UPDATE OF users_active_month ON community
FOR EACH ROW
EXECUTE FUNCTION r.search_combined_community_score_update ();

View file

@ -225,7 +225,7 @@ BEGIN
COALESCE(sum(comments + upvotes + downvotes)::bigint, 0) AS count_,
community_id AS community_id_
FROM
post_aggregates
post
WHERE
published >= (CURRENT_TIMESTAMP - i::interval)
GROUP BY

View file

@ -1,153 +0,0 @@
use crate::{
aggregates::structs::CommentAggregates,
newtypes::CommentId,
schema::comment_aggregates,
utils::{functions::hot_rank, get_conn, DbPool},
};
use diesel::{result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
impl CommentAggregates {
pub async fn read(pool: &mut DbPool<'_>, comment_id: CommentId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
comment_aggregates::table.find(comment_id).first(conn).await
}
pub async fn update_hot_rank(
pool: &mut DbPool<'_>,
comment_id: CommentId,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(comment_aggregates::table.find(comment_id))
.set(comment_aggregates::hot_rank.eq(hot_rank(
comment_aggregates::score,
comment_aggregates::published,
)))
.get_result::<Self>(conn)
.await
}
}
#[cfg(test)]
mod tests {
use crate::{
aggregates::comment_aggregates::CommentAggregates,
source::{
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm},
community::{Community, CommunityInsertForm},
instance::Instance,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm},
},
traits::{Crud, Likeable},
utils::build_db_pool_for_tests,
};
use diesel::result::Error;
use pretty_assertions::assert_eq;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_crud() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_comment_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_comment_agg");
let another_inserted_person = Person::create(pool, &another_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_comment_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let _inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let comment_like = CommentLikeForm {
comment_id: inserted_comment.id,
person_id: inserted_person.id,
score: 1,
};
CommentLike::like(pool, &comment_like).await?;
let comment_aggs_before_delete = CommentAggregates::read(pool, inserted_comment.id).await?;
assert_eq!(1, comment_aggs_before_delete.score);
assert_eq!(1, comment_aggs_before_delete.upvotes);
assert_eq!(0, comment_aggs_before_delete.downvotes);
// Add a post dislike from the other person
let comment_dislike = CommentLikeForm {
comment_id: inserted_comment.id,
person_id: another_inserted_person.id,
score: -1,
};
CommentLike::like(pool, &comment_dislike).await?;
let comment_aggs_after_dislike = CommentAggregates::read(pool, inserted_comment.id).await?;
assert_eq!(0, comment_aggs_after_dislike.score);
assert_eq!(1, comment_aggs_after_dislike.upvotes);
assert_eq!(1, comment_aggs_after_dislike.downvotes);
// Remove the first comment like
CommentLike::remove(pool, inserted_person.id, inserted_comment.id).await?;
let after_like_remove = CommentAggregates::read(pool, inserted_comment.id).await?;
assert_eq!(-1, after_like_remove.score);
assert_eq!(0, after_like_remove.upvotes);
assert_eq!(1, after_like_remove.downvotes);
// Remove the parent post
Post::delete(pool, inserted_post.id).await?;
// Should be none found, since the post was deleted
let after_delete = CommentAggregates::read(pool, inserted_comment.id).await;
assert!(after_delete.is_err());
// This should delete all the associated rows, and fire triggers
Person::delete(pool, another_inserted_person.id).await?;
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
}

View file

@ -1,197 +0,0 @@
use crate::{
aggregates::structs::CommunityAggregates,
newtypes::CommunityId,
schema::{community_aggregates, community_aggregates::subscribers},
utils::{get_conn, DbPool},
};
use diesel::{result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
impl CommunityAggregates {
pub async fn read(pool: &mut DbPool<'_>, for_community_id: CommunityId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
community_aggregates::table
.find(for_community_id)
.first(conn)
.await
}
pub async fn update_federated_followers(
pool: &mut DbPool<'_>,
for_community_id: CommunityId,
new_subscribers: i32,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let new_subscribers: i64 = new_subscribers.into();
diesel::update(community_aggregates::table.find(for_community_id))
.set(subscribers.eq(new_subscribers))
.get_result(conn)
.await
}
}
#[cfg(test)]
mod tests {
use crate::{
aggregates::community_aggregates::CommunityAggregates,
source::{
comment::{Comment, CommentInsertForm},
community::{
Community,
CommunityFollower,
CommunityFollowerForm,
CommunityFollowerState,
CommunityInsertForm,
},
instance::Instance,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm},
},
traits::{Crud, Followable},
utils::build_db_pool_for_tests,
};
use diesel::result::Error;
use pretty_assertions::assert_eq;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_crud() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg");
let another_inserted_person = Person::create(pool, &another_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_community_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let another_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_community_agg_2".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let another_inserted_community = Community::create(pool, &another_community).await?;
let first_person_follow = CommunityFollowerForm {
community_id: inserted_community.id,
person_id: inserted_person.id,
state: Some(CommunityFollowerState::Accepted),
approver_id: None,
};
CommunityFollower::follow(pool, &first_person_follow).await?;
let second_person_follow = CommunityFollowerForm {
community_id: inserted_community.id,
person_id: another_inserted_person.id,
state: Some(CommunityFollowerState::Accepted),
approver_id: None,
};
CommunityFollower::follow(pool, &second_person_follow).await?;
let another_community_follow = CommunityFollowerForm {
community_id: another_inserted_community.id,
person_id: inserted_person.id,
state: Some(CommunityFollowerState::Accepted),
approver_id: None,
};
CommunityFollower::follow(pool, &another_community_follow).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let _inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let community_aggregates_before_delete =
CommunityAggregates::read(pool, inserted_community.id).await?;
assert_eq!(2, community_aggregates_before_delete.subscribers);
assert_eq!(2, community_aggregates_before_delete.subscribers_local);
assert_eq!(1, community_aggregates_before_delete.posts);
assert_eq!(2, community_aggregates_before_delete.comments);
// Test the other community
let another_community_aggs =
CommunityAggregates::read(pool, another_inserted_community.id).await?;
assert_eq!(1, another_community_aggs.subscribers);
assert_eq!(1, another_community_aggs.subscribers_local);
assert_eq!(0, another_community_aggs.posts);
assert_eq!(0, another_community_aggs.comments);
// Unfollow test
CommunityFollower::unfollow(pool, &second_person_follow).await?;
let after_unfollow = CommunityAggregates::read(pool, inserted_community.id).await?;
assert_eq!(1, after_unfollow.subscribers);
assert_eq!(1, after_unfollow.subscribers_local);
// Follow again just for the later tests
CommunityFollower::follow(pool, &second_person_follow).await?;
let after_follow_again = CommunityAggregates::read(pool, inserted_community.id).await?;
assert_eq!(2, after_follow_again.subscribers);
assert_eq!(2, after_follow_again.subscribers_local);
// Remove a parent post (the comment count should also be 0)
Post::delete(pool, inserted_post.id).await?;
let after_parent_post_delete = CommunityAggregates::read(pool, inserted_community.id).await?;
assert_eq!(0, after_parent_post_delete.comments);
assert_eq!(0, after_parent_post_delete.posts);
// Remove the 2nd person
Person::delete(pool, another_inserted_person.id).await?;
let after_person_delete = CommunityAggregates::read(pool, inserted_community.id).await?;
assert_eq!(1, after_person_delete.subscribers);
assert_eq!(1, after_person_delete.subscribers_local);
// This should delete all the associated rows, and fire triggers
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
let another_community_num_deleted =
Community::delete(pool, another_inserted_community.id).await?;
assert_eq!(1, another_community_num_deleted);
// Should be none found, since the creator was deleted
let after_delete = CommunityAggregates::read(pool, inserted_community.id).await;
assert!(after_delete.is_err());
Ok(())
}
}

View file

@ -1,13 +0,0 @@
#[cfg(feature = "full")]
pub mod comment_aggregates;
#[cfg(feature = "full")]
pub mod community_aggregates;
#[cfg(feature = "full")]
pub mod person_aggregates;
#[cfg(feature = "full")]
pub mod person_post_aggregates;
#[cfg(feature = "full")]
pub mod post_aggregates;
#[cfg(feature = "full")]
pub mod site_aggregates;
pub mod structs;

View file

@ -1,182 +0,0 @@
use crate::{
aggregates::structs::PersonAggregates,
newtypes::PersonId,
schema::person_aggregates,
utils::{get_conn, DbPool},
};
use diesel::{result::Error, QueryDsl};
use diesel_async::RunQueryDsl;
impl PersonAggregates {
pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
person_aggregates::table.find(person_id).first(conn).await
}
}
#[cfg(test)]
mod tests {
use crate::{
aggregates::person_aggregates::PersonAggregates,
source::{
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
community::{Community, CommunityInsertForm},
instance::Instance,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm, PostLike, PostLikeForm},
},
traits::{Crud, Likeable},
utils::build_db_pool_for_tests,
};
use diesel::result::Error;
use pretty_assertions::assert_eq;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_crud() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_user_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_user_agg");
let another_inserted_person = Person::create(pool, &another_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_site_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let post_like = PostLikeForm::new(inserted_post.id, inserted_person.id, 1);
let _inserted_post_like = PostLike::like(pool, &post_like).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let mut comment_like = CommentLikeForm {
comment_id: inserted_comment.id,
person_id: inserted_person.id,
score: 1,
};
let _inserted_comment_like = CommentLike::like(pool, &comment_like).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let child_comment_like = CommentLikeForm {
comment_id: inserted_child_comment.id,
person_id: another_inserted_person.id,
score: 1,
};
let _inserted_child_comment_like = CommentLike::like(pool, &child_comment_like).await?;
let person_aggregates_before_delete = PersonAggregates::read(pool, inserted_person.id).await?;
assert_eq!(1, person_aggregates_before_delete.post_count);
assert_eq!(1, person_aggregates_before_delete.post_score);
assert_eq!(2, person_aggregates_before_delete.comment_count);
assert_eq!(2, person_aggregates_before_delete.comment_score);
// Remove a post like
PostLike::remove(pool, inserted_person.id, inserted_post.id).await?;
let after_post_like_remove = PersonAggregates::read(pool, inserted_person.id).await?;
assert_eq!(0, after_post_like_remove.post_score);
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
Comment::update(
pool,
inserted_child_comment.id,
&CommentUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
let after_parent_comment_removed = PersonAggregates::read(pool, inserted_person.id).await?;
assert_eq!(0, after_parent_comment_removed.comment_count);
// TODO: fix person aggregate comment score calculation
// assert_eq!(0, after_parent_comment_removed.comment_score);
// Remove a parent comment (the scores should also be removed)
Comment::delete(pool, inserted_comment.id).await?;
Comment::delete(pool, inserted_child_comment.id).await?;
let after_parent_comment_delete = PersonAggregates::read(pool, inserted_person.id).await?;
assert_eq!(0, after_parent_comment_delete.comment_count);
// TODO: fix person aggregate comment score calculation
// assert_eq!(0, after_parent_comment_delete.comment_score);
// Add in the two comments again, then delete the post.
let new_parent_comment = Comment::create(pool, &comment_form, None).await?;
let _new_child_comment =
Comment::create(pool, &child_comment_form, Some(&new_parent_comment.path)).await?;
comment_like.comment_id = new_parent_comment.id;
CommentLike::like(pool, &comment_like).await?;
let after_comment_add = PersonAggregates::read(pool, inserted_person.id).await?;
assert_eq!(2, after_comment_add.comment_count);
// TODO: fix person aggregate comment score calculation
// assert_eq!(1, after_comment_add.comment_score);
Post::delete(pool, inserted_post.id).await?;
let after_post_delete = PersonAggregates::read(pool, inserted_person.id).await?;
// TODO: fix person aggregate comment score calculation
// assert_eq!(0, after_post_delete.comment_score);
assert_eq!(0, after_post_delete.comment_count);
assert_eq!(0, after_post_delete.post_score);
assert_eq!(0, after_post_delete.post_count);
// This should delete all the associated rows, and fire triggers
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
Person::delete(pool, another_inserted_person.id).await?;
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
// Should be none found
let after_delete = PersonAggregates::read(pool, inserted_person.id).await;
assert!(after_delete.is_err());
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
}

View file

@ -1,270 +0,0 @@
use crate::{
aggregates::structs::PostAggregates,
newtypes::PostId,
schema::{community_aggregates, post, post_aggregates},
utils::{
functions::{hot_rank, scaled_rank},
get_conn,
DbPool,
},
};
use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl};
use diesel_async::RunQueryDsl;
impl PostAggregates {
pub async fn read(pool: &mut DbPool<'_>, post_id: PostId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
post_aggregates::table.find(post_id).first(conn).await
}
pub async fn update_ranks(pool: &mut DbPool<'_>, post_id: PostId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
// Diesel can't update based on a join, which is necessary for the scaled_rank
// https://github.com/diesel-rs/diesel/issues/1478
// Just select the metrics we need manually, for now, since its a single post anyway
let interactions_month = community_aggregates::table
.select(community_aggregates::interactions_month)
.inner_join(post::table.on(community_aggregates::community_id.eq(post::community_id)))
.filter(post::id.eq(post_id))
.first::<i64>(conn)
.await?;
diesel::update(post_aggregates::table.find(post_id))
.set((
post_aggregates::hot_rank.eq(hot_rank(post_aggregates::score, post_aggregates::published)),
post_aggregates::hot_rank_active.eq(hot_rank(
post_aggregates::score,
post_aggregates::newest_comment_time_necro,
)),
post_aggregates::scaled_rank.eq(scaled_rank(
post_aggregates::score,
post_aggregates::published,
interactions_month,
)),
))
.get_result::<Self>(conn)
.await
}
}
#[cfg(test)]
mod tests {
use crate::{
aggregates::post_aggregates::PostAggregates,
source::{
comment::{Comment, CommentInsertForm, CommentUpdateForm},
community::{Community, CommunityInsertForm},
instance::Instance,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm, PostLike, PostLikeForm},
},
traits::{Crud, Likeable},
utils::build_db_pool_for_tests,
};
use diesel::result::Error;
use pretty_assertions::assert_eq;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_crud() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg");
let another_inserted_person = Person::create(pool, &another_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_community_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let post_like = PostLikeForm::new(inserted_post.id, inserted_person.id, 1);
PostLike::like(pool, &post_like).await?;
let post_aggs_before_delete = PostAggregates::read(pool, inserted_post.id).await?;
assert_eq!(2, post_aggs_before_delete.comments);
assert_eq!(1, post_aggs_before_delete.score);
assert_eq!(1, post_aggs_before_delete.upvotes);
assert_eq!(0, post_aggs_before_delete.downvotes);
// Add a post dislike from the other person
let post_dislike = PostLikeForm::new(inserted_post.id, another_inserted_person.id, -1);
PostLike::like(pool, &post_dislike).await?;
let post_aggs_after_dislike = PostAggregates::read(pool, inserted_post.id).await?;
assert_eq!(2, post_aggs_after_dislike.comments);
assert_eq!(0, post_aggs_after_dislike.score);
assert_eq!(1, post_aggs_after_dislike.upvotes);
assert_eq!(1, post_aggs_after_dislike.downvotes);
// Remove the comments
Comment::delete(pool, inserted_comment.id).await?;
Comment::delete(pool, inserted_child_comment.id).await?;
let after_comment_delete = PostAggregates::read(pool, inserted_post.id).await?;
assert_eq!(0, after_comment_delete.comments);
assert_eq!(0, after_comment_delete.score);
assert_eq!(1, after_comment_delete.upvotes);
assert_eq!(1, after_comment_delete.downvotes);
// Remove the first post like
PostLike::remove(pool, inserted_person.id, inserted_post.id).await?;
let after_like_remove = PostAggregates::read(pool, inserted_post.id).await?;
assert_eq!(0, after_like_remove.comments);
assert_eq!(-1, after_like_remove.score);
assert_eq!(0, after_like_remove.upvotes);
assert_eq!(1, after_like_remove.downvotes);
// This should delete all the associated rows, and fire triggers
Person::delete(pool, another_inserted_person.id).await?;
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
// Should be none found, since the creator was deleted
let after_delete = PostAggregates::read(pool, inserted_post.id).await;
assert!(after_delete.is_err());
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_soft_delete() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_community_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let post_aggregates_before = PostAggregates::read(pool, inserted_post.id).await?;
assert_eq!(1, post_aggregates_before.comments);
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
let post_aggregates_after_remove = PostAggregates::read(pool, inserted_post.id).await?;
assert_eq!(0, post_aggregates_after_remove.comments);
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
removed: Some(false),
..Default::default()
},
)
.await?;
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
deleted: Some(true),
..Default::default()
},
)
.await?;
let post_aggregates_after_delete = PostAggregates::read(pool, inserted_post.id).await?;
assert_eq!(0, post_aggregates_after_delete.comments);
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
let post_aggregates_after_delete_remove = PostAggregates::read(pool, inserted_post.id).await?;
assert_eq!(0, post_aggregates_after_delete_remove.comments);
Comment::delete(pool, inserted_comment.id).await?;
Post::delete(pool, inserted_post.id).await?;
Person::delete(pool, inserted_person.id).await?;
Community::delete(pool, inserted_community.id).await?;
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
}

View file

@ -1,204 +0,0 @@
use crate::{
aggregates::structs::SiteAggregates,
schema::site_aggregates,
utils::{get_conn, DbPool},
};
use diesel::result::Error;
use diesel_async::RunQueryDsl;
impl SiteAggregates {
pub async fn read(pool: &mut DbPool<'_>) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
site_aggregates::table.first(conn).await
}
}
#[cfg(test)]
mod tests {
use crate::{
aggregates::site_aggregates::SiteAggregates,
source::{
comment::{Comment, CommentInsertForm},
community::{Community, CommunityInsertForm, CommunityUpdateForm},
instance::Instance,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm},
site::{Site, SiteInsertForm},
},
traits::Crud,
utils::{build_db_pool_for_tests, DbPool},
};
use diesel::result::Error;
use pretty_assertions::assert_eq;
use serial_test::serial;
async fn prepare_site_with_community(
pool: &mut DbPool<'_>,
) -> Result<(Instance, Person, Site, Community), Error> {
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_site_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let site_form = SiteInsertForm::new("test_site".into(), inserted_instance.id);
let inserted_site = Site::create(pool, &site_form).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_site_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
Ok((
inserted_instance,
inserted_person,
inserted_site,
inserted_community,
))
}
#[tokio::test]
#[serial]
async fn test_crud() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let (inserted_instance, inserted_person, inserted_site, inserted_community) =
prepare_site_with_community(pool).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
// Insert two of those posts
let inserted_post = Post::create(pool, &new_post).await?;
let _inserted_post_again = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
// Insert two of those comments
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let _inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let site_aggregates_before_delete = SiteAggregates::read(pool).await?;
// TODO: this is unstable, sometimes it returns 0 users, sometimes 1
//assert_eq!(0, site_aggregates_before_delete.users);
assert_eq!(1, site_aggregates_before_delete.communities);
assert_eq!(2, site_aggregates_before_delete.posts);
assert_eq!(2, site_aggregates_before_delete.comments);
// Try a post delete
Post::delete(pool, inserted_post.id).await?;
let site_aggregates_after_post_delete = SiteAggregates::read(pool).await?;
assert_eq!(1, site_aggregates_after_post_delete.posts);
assert_eq!(0, site_aggregates_after_post_delete.comments);
// This shouuld delete all the associated rows, and fire triggers
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
// Site should still exist, it can without a site creator.
let after_delete_creator = SiteAggregates::read(pool).await;
assert!(after_delete_creator.is_ok());
Site::delete(pool, inserted_site.id).await?;
let after_delete_site = SiteAggregates::read(pool).await;
assert!(after_delete_site.is_err());
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_soft_delete() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let (inserted_instance, inserted_person, inserted_site, inserted_community) =
prepare_site_with_community(pool).await?;
let site_aggregates_before = SiteAggregates::read(pool).await?;
assert_eq!(1, site_aggregates_before.communities);
Community::update(
pool,
inserted_community.id,
&CommunityUpdateForm {
deleted: Some(true),
..Default::default()
},
)
.await?;
let site_aggregates_after_delete = SiteAggregates::read(pool).await?;
assert_eq!(0, site_aggregates_after_delete.communities);
Community::update(
pool,
inserted_community.id,
&CommunityUpdateForm {
deleted: Some(false),
..Default::default()
},
)
.await?;
Community::update(
pool,
inserted_community.id,
&CommunityUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
let site_aggregates_after_remove = SiteAggregates::read(pool).await?;
assert_eq!(0, site_aggregates_after_remove.communities);
Community::update(
pool,
inserted_community.id,
&CommunityUpdateForm {
deleted: Some(true),
..Default::default()
},
)
.await?;
let site_aggregates_after_remove_delete = SiteAggregates::read(pool).await?;
assert_eq!(0, site_aggregates_after_remove_delete.communities);
Community::delete(pool, inserted_community.id).await?;
Site::delete(pool, inserted_site.id).await?;
Person::delete(pool, inserted_person.id).await?;
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
}

View file

@ -1,220 +0,0 @@
use crate::newtypes::{CommentId, CommunityId, InstanceId, PersonId, PostId, SiteId};
#[cfg(feature = "full")]
use crate::schema::{
comment_aggregates,
community_aggregates,
person_aggregates,
post_actions,
post_aggregates,
site_aggregates,
};
use chrono::{DateTime, Utc};
#[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
#[cfg(feature = "full")]
use i_love_jesus::CursorKeysModule;
use serde::{Deserialize, Serialize};
#[cfg(feature = "full")]
use ts_rs::TS;
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
)]
#[cfg_attr(feature = "full", diesel(table_name = comment_aggregates))]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))]
#[cfg_attr(feature = "full", diesel(primary_key(comment_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// Aggregate data for a comment.
pub struct CommentAggregates {
pub comment_id: CommentId,
pub score: i64,
pub upvotes: i64,
pub downvotes: i64,
pub published: DateTime<Utc>,
/// The total number of children in this comment branch.
pub child_count: i32,
#[serde(skip)]
pub hot_rank: f64,
#[serde(skip)]
pub controversy_rank: f64,
pub report_count: i16,
pub unresolved_report_count: i16,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
)]
#[cfg_attr(feature = "full", diesel(table_name = community_aggregates))]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::community::Community))
)]
#[cfg_attr(feature = "full", diesel(primary_key(community_id)))]
#[cfg_attr(feature = "full", ts(export))]
/// Aggregate data for a community.
pub struct CommunityAggregates {
pub community_id: CommunityId,
pub subscribers: i64,
pub posts: i64,
pub comments: i64,
pub published: DateTime<Utc>,
/// The number of users with any activity in the last day.
pub users_active_day: i64,
/// The number of users with any activity in the last week.
pub users_active_week: i64,
/// The number of users with any activity in the last month.
pub users_active_month: i64,
/// The number of users with any activity in the last year.
pub users_active_half_year: i64,
#[serde(skip)]
pub hot_rank: f64,
pub subscribers_local: i64,
pub report_count: i16,
pub unresolved_report_count: i16,
/// Number of any interactions over the last month.
#[serde(skip)]
pub interactions_month: i64,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
)]
#[cfg_attr(feature = "full", diesel(table_name = person_aggregates))]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// Aggregate data for a person.
pub struct PersonAggregates {
pub person_id: PersonId,
pub post_count: i64,
#[serde(skip)]
pub post_score: i64,
pub comment_count: i64,
#[serde(skip)]
pub comment_score: i64,
pub published: DateTime<Utc>,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "full",
derive(
Queryable,
Selectable,
Associations,
Identifiable,
TS,
CursorKeysModule
)
)]
#[cfg_attr(feature = "full", diesel(table_name = post_aggregates))]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
#[cfg_attr(feature = "full", diesel(primary_key(post_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
#[cfg_attr(feature = "full", cursor_keys_module(name = post_aggregates_keys))]
/// Aggregate data for a post.
pub struct PostAggregates {
pub post_id: PostId,
pub comments: i64,
pub score: i64,
pub upvotes: i64,
pub downvotes: i64,
pub published: DateTime<Utc>,
#[serde(skip)]
/// A newest comment time, limited to 2 days, to prevent necrobumping
pub newest_comment_time_necro: DateTime<Utc>,
/// The time of the newest comment in the post.
pub newest_comment_time: DateTime<Utc>,
/// If the post is featured on the community.
#[serde(skip)]
pub featured_community: bool,
/// If the post is featured on the site / to local.
#[serde(skip)]
pub featured_local: bool,
#[serde(skip)]
pub hot_rank: f64,
#[serde(skip)]
pub hot_rank_active: f64,
#[serde(skip)]
pub community_id: CommunityId,
#[serde(skip)]
pub creator_id: PersonId,
#[serde(skip)]
pub controversy_rank: f64,
#[serde(skip)]
pub instance_id: InstanceId,
/// A rank that amplifies smaller communities
#[serde(skip)]
pub scaled_rank: f64,
pub report_count: i16,
pub unresolved_report_count: i16,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable)
)]
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
/// Aggregate data for a person's post.
pub struct PersonPostAggregates {
pub person_id: PersonId,
pub post_id: PostId,
/// The number of comments they've read on that post.
///
/// This is updated to the current post comment count every time they view a post.
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::read_comments_amount.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::read_comments_amount>))]
pub read_comments: i64,
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::read_comments.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::read_comments>))]
pub published: DateTime<Utc>,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
pub struct PersonPostAggregatesForm {
pub person_id: PersonId,
pub post_id: PostId,
#[cfg_attr(feature = "full", diesel(column_name = read_comments_amount))]
pub read_comments: i64,
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy, Hash)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
)]
#[cfg_attr(feature = "full", diesel(table_name = site_aggregates))]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::site::Site)))]
#[cfg_attr(feature = "full", diesel(primary_key(site_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// Aggregate data for a site.
pub struct SiteAggregates {
pub site_id: SiteId,
pub users: i64,
pub posts: i64,
pub comments: i64,
pub communities: i64,
/// The number of users with any activity in the last day.
pub users_active_day: i64,
/// The number of users with any activity in the last week.
pub users_active_week: i64,
/// The number of users with any activity in the last month.
pub users_active_month: i64,
/// The number of users with any activity in the last half year.
pub users_active_half_year: i64,
}

View file

@ -13,7 +13,14 @@ use crate::{
CommentUpdateForm,
},
traits::{Crud, Likeable, Saveable},
utils::{functions::coalesce, get_conn, now, uplete, DbPool, DELETED_REPLACEMENT_TEXT},
utils::{
functions::{coalesce, hot_rank},
get_conn,
now,
uplete,
DbPool,
DELETED_REPLACEMENT_TEXT,
},
};
use chrono::{DateTime, Utc};
use diesel::{
@ -119,7 +126,17 @@ impl Comment {
None
}
}
pub async fn update_hot_rank(
pool: &mut DbPool<'_>,
comment_id: CommentId,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(comment::table.find(comment_id))
.set(comment::hot_rank.eq(hot_rank(comment::score, comment::published)))
.get_result::<Self>(conn)
.await
}
pub fn local_url(&self, settings: &Settings) -> LemmyResult<DbUrl> {
let domain = settings.get_protocol_and_hostname();
Ok(Url::parse(&format!("{domain}/comment/{}", self.id))?.into())
@ -150,6 +167,14 @@ pub fn comment_select_remove_deletes() -> _ {
comment::path,
comment::distinguished,
comment::language_id,
comment::score,
comment::upvotes,
comment::downvotes,
comment::child_count,
comment::hot_rank,
comment::controversy_rank,
comment::report_count,
comment::unresolved_report_count,
)
}
@ -242,25 +267,17 @@ impl Saveable for CommentSaved {
#[cfg(test)]
mod tests {
use super::*;
use crate::{
newtypes::LanguageId,
source::{
comment::{
Comment,
CommentInsertForm,
CommentLike,
CommentLikeForm,
CommentSaved,
CommentSavedForm,
CommentUpdateForm,
},
community::{Community, CommunityInsertForm},
instance::Instance,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm},
},
traits::{Crud, Likeable, Saveable},
utils::{build_db_pool_for_tests, uplete},
utils::{build_db_pool_for_tests, uplete, RANK_DEFAULT},
};
use diesel_ltree::Ltree;
use lemmy_utils::error::LemmyResult;
@ -320,6 +337,14 @@ mod tests {
distinguished: false,
local: true,
language_id: LanguageId::default(),
child_count: 1,
controversy_rank: 0.0,
downvotes: 0,
upvotes: 1,
score: 1,
hot_rank: RANK_DEFAULT,
report_count: 0,
unresolved_report_count: 0,
};
let child_comment_form = CommentInsertForm::new(
@ -374,7 +399,6 @@ mod tests {
Instance::delete(pool, inserted_instance.id).await?;
assert_eq!(expected_comment, read_comment);
assert_eq!(expected_comment, inserted_comment);
assert_eq!(expected_comment, updated_comment);
assert_eq!(expected_comment_like, inserted_comment_like);
assert_eq!(expected_comment_saved, inserted_comment_saved);
@ -388,4 +412,107 @@ mod tests {
Ok(())
}
#[tokio::test]
#[serial]
async fn test_aggregates() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_comment_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_comment_agg");
let another_inserted_person = Person::create(pool, &another_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_comment_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let _inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let comment_like = CommentLikeForm {
comment_id: inserted_comment.id,
person_id: inserted_person.id,
score: 1,
};
CommentLike::like(pool, &comment_like).await?;
let comment_aggs_before_delete = Comment::read(pool, inserted_comment.id).await?;
assert_eq!(1, comment_aggs_before_delete.score);
assert_eq!(1, comment_aggs_before_delete.upvotes);
assert_eq!(0, comment_aggs_before_delete.downvotes);
// Add a post dislike from the other person
let comment_dislike = CommentLikeForm {
comment_id: inserted_comment.id,
person_id: another_inserted_person.id,
score: -1,
};
CommentLike::like(pool, &comment_dislike).await?;
let comment_aggs_after_dislike = Comment::read(pool, inserted_comment.id).await?;
assert_eq!(0, comment_aggs_after_dislike.score);
assert_eq!(1, comment_aggs_after_dislike.upvotes);
assert_eq!(1, comment_aggs_after_dislike.downvotes);
// Remove the first comment like
CommentLike::remove(pool, inserted_person.id, inserted_comment.id).await?;
let after_like_remove = Comment::read(pool, inserted_comment.id).await?;
assert_eq!(-1, after_like_remove.score);
assert_eq!(0, after_like_remove.upvotes);
assert_eq!(1, after_like_remove.downvotes);
// Remove the parent post
Post::delete(pool, inserted_post.id).await?;
// Should be none found, since the post was deleted
let after_delete = Comment::read(pool, inserted_comment.id).await;
assert!(after_delete.is_err());
// This should delete all the associated rows, and fire triggers
Person::delete(pool, another_inserted_person.id).await?;
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
}

View file

@ -280,6 +280,19 @@ impl Community {
let domain = settings.get_protocol_and_hostname();
Ok(Url::parse(&format!("{domain}/c/{name}"))?.into())
}
pub async fn update_federated_followers(
pool: &mut DbPool<'_>,
for_community_id: CommunityId,
new_subscribers: i32,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let new_subscribers: i64 = new_subscribers.into();
diesel::update(community::table.find(for_community_id))
.set(community::dsl::subscribers.eq(new_subscribers))
.get_result(conn)
.await
}
}
impl CommunityModerator {
@ -551,6 +564,7 @@ impl ApubActor for Community {
mod tests {
use crate::{
source::{
comment::{Comment, CommentInsertForm},
community::{
Community,
CommunityFollower,
@ -566,9 +580,10 @@ mod tests {
instance::Instance,
local_user::LocalUser,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm},
},
traits::{Bannable, Crud, Followable, Joinable},
utils::{build_db_pool_for_tests, uplete},
utils::{build_db_pool_for_tests, uplete, RANK_DEFAULT},
CommunityVisibility,
};
use lemmy_utils::error::LemmyResult;
@ -624,6 +639,18 @@ mod tests {
instance_id: inserted_instance.id,
visibility: CommunityVisibility::Public,
random_number: inserted_community.random_number,
subscribers: 1,
posts: 0,
comments: 0,
users_active_day: 0,
users_active_week: 0,
users_active_month: 0,
users_active_half_year: 0,
hot_rank: RANK_DEFAULT,
subscribers_local: 1,
report_count: 0,
unresolved_report_count: 0,
interactions_month: 0,
};
let community_follower_form = CommunityFollowerForm {
@ -731,7 +758,6 @@ mod tests {
Instance::delete(pool, inserted_instance.id).await?;
assert_eq!(expected_community, read_community);
assert_eq!(expected_community, inserted_community);
assert_eq!(expected_community, updated_community);
assert_eq!(expected_community_follower, inserted_community_follower);
assert_eq!(expected_community_moderator, inserted_bobby_moderator);
@ -744,4 +770,142 @@ mod tests {
Ok(())
}
#[tokio::test]
#[serial]
async fn test_aggregates() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg");
let another_inserted_person = Person::create(pool, &another_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_community_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let another_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_community_agg_2".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let another_inserted_community = Community::create(pool, &another_community).await?;
let first_person_follow = CommunityFollowerForm {
community_id: inserted_community.id,
person_id: inserted_person.id,
state: Some(CommunityFollowerState::Accepted),
approver_id: None,
};
CommunityFollower::follow(pool, &first_person_follow).await?;
let second_person_follow = CommunityFollowerForm {
community_id: inserted_community.id,
person_id: another_inserted_person.id,
state: Some(CommunityFollowerState::Accepted),
approver_id: None,
};
CommunityFollower::follow(pool, &second_person_follow).await?;
let another_community_follow = CommunityFollowerForm {
community_id: another_inserted_community.id,
person_id: inserted_person.id,
state: Some(CommunityFollowerState::Accepted),
approver_id: None,
};
CommunityFollower::follow(pool, &another_community_follow).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let _inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let community_aggregates_before_delete = Community::read(pool, inserted_community.id).await?;
assert_eq!(2, community_aggregates_before_delete.subscribers);
assert_eq!(2, community_aggregates_before_delete.subscribers_local);
assert_eq!(1, community_aggregates_before_delete.posts);
assert_eq!(2, community_aggregates_before_delete.comments);
// Test the other community
let another_community_aggs = Community::read(pool, another_inserted_community.id).await?;
assert_eq!(1, another_community_aggs.subscribers);
assert_eq!(1, another_community_aggs.subscribers_local);
assert_eq!(0, another_community_aggs.posts);
assert_eq!(0, another_community_aggs.comments);
// Unfollow test
CommunityFollower::unfollow(pool, &second_person_follow).await?;
let after_unfollow = Community::read(pool, inserted_community.id).await?;
assert_eq!(1, after_unfollow.subscribers);
assert_eq!(1, after_unfollow.subscribers_local);
// Follow again just for the later tests
CommunityFollower::follow(pool, &second_person_follow).await?;
let after_follow_again = Community::read(pool, inserted_community.id).await?;
assert_eq!(2, after_follow_again.subscribers);
assert_eq!(2, after_follow_again.subscribers_local);
// Remove a parent post (the comment count should also be 0)
Post::delete(pool, inserted_post.id).await?;
let after_parent_post_delete = Community::read(pool, inserted_community.id).await?;
assert_eq!(0, after_parent_post_delete.posts);
assert_eq!(0, after_parent_post_delete.comments);
// Remove the 2nd person
Person::delete(pool, another_inserted_person.id).await?;
let after_person_delete = Community::read(pool, inserted_community.id).await?;
assert_eq!(1, after_person_delete.subscribers);
assert_eq!(1, after_person_delete.subscribers_local);
// This should delete all the associated rows, and fire triggers
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
let another_community_num_deleted =
Community::delete(pool, another_inserted_community.id).await?;
assert_eq!(1, another_community_num_deleted);
// Should be none found, since the creator was deleted
let after_delete = Community::read(pool, inserted_community.id).await;
assert!(after_delete.is_err());
Ok(())
}
}

View file

@ -39,3 +39,195 @@ impl LocalSite {
diesel::delete(local_site::table).execute(conn).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
source::{
comment::{Comment, CommentInsertForm},
community::{Community, CommunityInsertForm, CommunityUpdateForm},
instance::Instance,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm},
site::{Site, SiteInsertForm},
},
traits::Crud,
utils::{build_db_pool_for_tests, DbPool},
};
use pretty_assertions::assert_eq;
use serial_test::serial;
async fn prepare_site_with_community(
pool: &mut DbPool<'_>,
) -> LemmyResult<(Instance, Person, Site, Community)> {
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_site_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let site_form = SiteInsertForm::new("test_site".into(), inserted_instance.id);
let inserted_site = Site::create(pool, &site_form).await?;
let local_site_form = LocalSiteInsertForm::new(inserted_site.id);
LocalSite::create(pool, &local_site_form).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_site_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
Ok((
inserted_instance,
inserted_person,
inserted_site,
inserted_community,
))
}
#[tokio::test]
#[serial]
async fn test_aggregates() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let (inserted_instance, inserted_person, inserted_site, inserted_community) =
prepare_site_with_community(pool).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
// Insert two of those posts
let inserted_post = Post::create(pool, &new_post).await?;
let _inserted_post_again = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
// Insert two of those comments
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let _inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let site_aggregates_before_delete = LocalSite::read(pool).await?;
// TODO: this is unstable, sometimes it returns 0 users, sometimes 1
//assert_eq!(0, site_aggregates_before_delete.users);
assert_eq!(1, site_aggregates_before_delete.communities);
assert_eq!(2, site_aggregates_before_delete.posts);
assert_eq!(2, site_aggregates_before_delete.comments);
// Try a post delete
Post::delete(pool, inserted_post.id).await?;
let site_aggregates_after_post_delete = LocalSite::read(pool).await?;
assert_eq!(1, site_aggregates_after_post_delete.posts);
assert_eq!(0, site_aggregates_after_post_delete.comments);
// This shouuld delete all the associated rows, and fire triggers
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
// Site should still exist, it can without a site creator.
let after_delete_creator = LocalSite::read(pool).await;
assert!(after_delete_creator.is_ok());
Site::delete(pool, inserted_site.id).await?;
let after_delete_site = LocalSite::read(pool).await;
assert!(after_delete_site.is_err());
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_soft_delete() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let (inserted_instance, inserted_person, inserted_site, inserted_community) =
prepare_site_with_community(pool).await?;
let site_aggregates_before = LocalSite::read(pool).await?;
assert_eq!(1, site_aggregates_before.communities);
Community::update(
pool,
inserted_community.id,
&CommunityUpdateForm {
deleted: Some(true),
..Default::default()
},
)
.await?;
let site_aggregates_after_delete = LocalSite::read(pool).await?;
assert_eq!(0, site_aggregates_after_delete.communities);
Community::update(
pool,
inserted_community.id,
&CommunityUpdateForm {
deleted: Some(false),
..Default::default()
},
)
.await?;
Community::update(
pool,
inserted_community.id,
&CommunityUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
let site_aggregates_after_remove = LocalSite::read(pool).await?;
assert_eq!(0, site_aggregates_after_remove.communities);
Community::update(
pool,
inserted_community.id,
&CommunityUpdateForm {
deleted: Some(true),
..Default::default()
},
)
.await?;
let site_aggregates_after_remove_delete = LocalSite::read(pool).await?;
assert_eq!(0, site_aggregates_after_remove_delete.communities);
Community::delete(pool, inserted_community.id).await?;
Site::delete(pool, inserted_site.id).await?;
Person::delete(pool, inserted_person.id).await?;
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
}

View file

@ -5,7 +5,6 @@ use crate::{
source::{
actor_language::LocalUserLanguage,
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
local_user_vote_display_mode::{LocalUserVoteDisplayMode, LocalUserVoteDisplayModeInsertForm},
site::Site,
},
utils::{
@ -55,10 +54,6 @@ impl LocalUser {
LocalUserLanguage::update(pool, languages, local_user_.id).await?;
// Create their vote_display_modes
let vote_display_mode_form = LocalUserVoteDisplayModeInsertForm::new(local_user_.id);
LocalUserVoteDisplayMode::create(pool, &vote_display_mode_form).await?;
Ok(local_user_)
}

View file

@ -1,60 +0,0 @@
use crate::{
diesel::OptionalExtension,
newtypes::LocalUserId,
schema::local_user_vote_display_mode,
source::local_user_vote_display_mode::{
LocalUserVoteDisplayMode,
LocalUserVoteDisplayModeInsertForm,
LocalUserVoteDisplayModeUpdateForm,
},
utils::{get_conn, DbPool},
};
use diesel::{dsl::insert_into, result::Error, QueryDsl};
use diesel_async::RunQueryDsl;
impl LocalUserVoteDisplayMode {
pub async fn read(pool: &mut DbPool<'_>) -> Result<Option<Self>, Error> {
let conn = &mut get_conn(pool).await?;
local_user_vote_display_mode::table
.first(conn)
.await
.optional()
}
pub async fn create(
pool: &mut DbPool<'_>,
form: &LocalUserVoteDisplayModeInsertForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(local_user_vote_display_mode::table)
.values(form)
.get_result::<Self>(conn)
.await
}
pub async fn update(
pool: &mut DbPool<'_>,
local_user_id: LocalUserId,
form: &LocalUserVoteDisplayModeUpdateForm,
) -> Result<(), Error> {
// avoid error "There are no changes to save. This query cannot be built"
if form.is_empty() {
return Ok(());
}
let conn = &mut get_conn(pool).await?;
diesel::update(local_user_vote_display_mode::table.find(local_user_id))
.set(form)
.get_result::<Self>(conn)
.await?;
Ok(())
}
}
impl LocalUserVoteDisplayModeUpdateForm {
fn is_empty(&self) -> bool {
self.score.is_none()
&& self.upvotes.is_none()
&& self.downvotes.is_none()
&& self.upvote_percentage.is_none()
}
}

View file

@ -20,7 +20,6 @@ pub mod local_site;
pub mod local_site_rate_limit;
pub mod local_site_url_blocklist;
pub mod local_user;
pub mod local_user_vote_display_mode;
pub mod login_token;
pub mod mod_log;
pub mod oauth_account;
@ -31,6 +30,7 @@ pub mod person_block;
pub mod person_comment_mention;
pub mod person_post_mention;
pub mod post;
pub mod post_actions;
pub mod post_report;
pub mod private_message;
pub mod private_message_report;

View file

@ -256,12 +256,16 @@ mod tests {
use crate::{
source::{
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
community::{Community, CommunityInsertForm},
instance::Instance,
person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm},
post::{Post, PostInsertForm, PostLike, PostLikeForm},
},
traits::{Crud, Followable},
traits::{Crud, Followable, Likeable},
utils::{build_db_pool_for_tests, uplete},
};
use diesel::result::Error;
use lemmy_utils::error::LemmyResult;
use pretty_assertions::assert_eq;
use serial_test::serial;
@ -299,6 +303,10 @@ mod tests {
matrix_user_id: None,
ban_expires: None,
instance_id: inserted_instance.id,
post_count: 0,
post_score: 0,
comment_count: 0,
comment_score: 0,
};
let read_person = Person::read(pool, inserted_person.id).await?;
@ -350,4 +358,151 @@ mod tests {
Ok(())
}
#[tokio::test]
#[serial]
async fn test_aggregates() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_user_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_user_agg");
let another_inserted_person = Person::create(pool, &another_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_site_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let post_like = PostLikeForm::new(inserted_post.id, inserted_person.id, 1);
let _inserted_post_like = PostLike::like(pool, &post_like).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let mut comment_like = CommentLikeForm {
comment_id: inserted_comment.id,
person_id: inserted_person.id,
score: 1,
};
let _inserted_comment_like = CommentLike::like(pool, &comment_like).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let child_comment_like = CommentLikeForm {
comment_id: inserted_child_comment.id,
person_id: another_inserted_person.id,
score: 1,
};
let _inserted_child_comment_like = CommentLike::like(pool, &child_comment_like).await?;
let person_aggregates_before_delete = Person::read(pool, inserted_person.id).await?;
assert_eq!(1, person_aggregates_before_delete.post_count);
assert_eq!(1, person_aggregates_before_delete.post_score);
assert_eq!(2, person_aggregates_before_delete.comment_count);
assert_eq!(2, person_aggregates_before_delete.comment_score);
// Remove a post like
PostLike::remove(pool, inserted_person.id, inserted_post.id).await?;
let after_post_like_remove = Person::read(pool, inserted_person.id).await?;
assert_eq!(0, after_post_like_remove.post_score);
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
Comment::update(
pool,
inserted_child_comment.id,
&CommentUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
let after_parent_comment_removed = Person::read(pool, inserted_person.id).await?;
assert_eq!(0, after_parent_comment_removed.comment_count);
// TODO: fix person aggregate comment score calculation
// assert_eq!(0, after_parent_comment_removed.comment_score);
// Remove a parent comment (the scores should also be removed)
Comment::delete(pool, inserted_comment.id).await?;
Comment::delete(pool, inserted_child_comment.id).await?;
let after_parent_comment_delete = Person::read(pool, inserted_person.id).await?;
assert_eq!(0, after_parent_comment_delete.comment_count);
// TODO: fix person aggregate comment score calculation
// assert_eq!(0, after_parent_comment_delete.comment_score);
// Add in the two comments again, then delete the post.
let new_parent_comment = Comment::create(pool, &comment_form, None).await?;
let _new_child_comment =
Comment::create(pool, &child_comment_form, Some(&new_parent_comment.path)).await?;
comment_like.comment_id = new_parent_comment.id;
CommentLike::like(pool, &comment_like).await?;
let after_comment_add = Person::read(pool, inserted_person.id).await?;
assert_eq!(2, after_comment_add.comment_count);
// TODO: fix person aggregate comment score calculation
// assert_eq!(1, after_comment_add.comment_score);
Post::delete(pool, inserted_post.id).await?;
let after_post_delete = Person::read(pool, inserted_person.id).await?;
// TODO: fix person aggregate comment score calculation
// assert_eq!(0, after_post_delete.comment_score);
assert_eq!(0, after_post_delete.comment_count);
assert_eq!(0, after_post_delete.post_score);
assert_eq!(0, after_post_delete.post_count);
// This should delete all the associated rows, and fire triggers
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
Person::delete(pool, another_inserted_person.id).await?;
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
// Should be none found
let after_delete = Person::read(pool, inserted_person.id).await;
assert!(after_delete.is_err());
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
}

View file

@ -17,7 +17,7 @@ use crate::{
},
traits::{Crud, Likeable, Saveable},
utils::{
functions::coalesce,
functions::{coalesce, hot_rank, scaled_rank},
get_conn,
now,
uplete,
@ -38,6 +38,7 @@ use diesel::{
BoolExpressionMethods,
DecoratableTarget,
ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods,
OptionalExtension,
QueryDsl,
@ -289,6 +290,33 @@ impl Post {
.await
}
pub async fn update_ranks(pool: &mut DbPool<'_>, post_id: PostId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
// Diesel can't update based on a join, which is necessary for the scaled_rank
// https://github.com/diesel-rs/diesel/issues/1478
// Just select the metrics we need manually, for now, since its a single post anyway
let interactions_month = community::table
.select(community::interactions_month)
.inner_join(post::table.on(community::id.eq(post::community_id)))
.filter(post::id.eq(post_id))
.first::<i64>(conn)
.await?;
diesel::update(post::table.find(post_id))
.set((
post::hot_rank.eq(hot_rank(post::score, post::published)),
post::hot_rank_active.eq(hot_rank(post::score, post::newest_comment_time_necro)),
post::scaled_rank.eq(scaled_rank(
post::score,
post::published,
interactions_month,
)),
))
.get_result::<Self>(conn)
.await
}
pub fn local_url(&self, settings: &Settings) -> LemmyResult<DbUrl> {
let domain = settings.get_protocol_and_hostname();
Ok(Url::parse(&format!("{domain}/post/{}", self.id))?.into())
@ -463,9 +491,9 @@ impl PostActionsCursor {
#[cfg(test)]
mod tests {
use crate::{
source::{
comment::{Comment, CommentInsertForm, CommentUpdateForm},
community::{Community, CommunityInsertForm},
instance::Instance,
person::{Person, PersonInsertForm},
@ -482,9 +510,10 @@ mod tests {
},
},
traits::{Crud, Likeable, Saveable},
utils::{build_db_pool_for_tests, uplete},
utils::{build_db_pool_for_tests, uplete, RANK_DEFAULT},
};
use chrono::DateTime;
use diesel::result::Error;
use lemmy_utils::error::LemmyResult;
use pretty_assertions::assert_eq;
use serial_test::serial;
@ -556,6 +585,19 @@ mod tests {
featured_local: false,
url_content_type: None,
scheduled_publish_time: None,
comments: 0,
controversy_rank: 0.0,
downvotes: 0,
upvotes: 1,
score: 1,
hot_rank: RANK_DEFAULT,
hot_rank_active: RANK_DEFAULT,
instance_id: inserted_instance.id,
newest_comment_time: inserted_post.published,
newest_comment_time_necro: inserted_post.published,
report_count: 0,
scaled_rank: RANK_DEFAULT,
unresolved_report_count: 0,
};
// Post Like
@ -622,11 +664,210 @@ mod tests {
Instance::delete(pool, inserted_instance.id).await?;
assert_eq!(expected_post, read_post);
assert_eq!(expected_post, inserted_post);
assert_eq!(expected_post, updated_post);
assert_eq!(expected_post_like, inserted_post_like);
assert_eq!(expected_post_saved, inserted_post_saved);
Ok(())
}
#[tokio::test]
#[serial]
async fn test_aggregates() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let another_person = PersonInsertForm::test_form(inserted_instance.id, "jerry_community_agg");
let another_inserted_person = Person::create(pool, &another_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_community_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let child_comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let post_like = PostLikeForm::new(inserted_post.id, inserted_person.id, 1);
PostLike::like(pool, &post_like).await?;
let post_aggs_before_delete = Post::read(pool, inserted_post.id).await?;
assert_eq!(2, post_aggs_before_delete.comments);
assert_eq!(1, post_aggs_before_delete.score);
assert_eq!(1, post_aggs_before_delete.upvotes);
assert_eq!(0, post_aggs_before_delete.downvotes);
// Add a post dislike from the other person
let post_dislike = PostLikeForm::new(inserted_post.id, another_inserted_person.id, -1);
PostLike::like(pool, &post_dislike).await?;
let post_aggs_after_dislike = Post::read(pool, inserted_post.id).await?;
assert_eq!(2, post_aggs_after_dislike.comments);
assert_eq!(0, post_aggs_after_dislike.score);
assert_eq!(1, post_aggs_after_dislike.upvotes);
assert_eq!(1, post_aggs_after_dislike.downvotes);
// Remove the comments
Comment::delete(pool, inserted_comment.id).await?;
Comment::delete(pool, inserted_child_comment.id).await?;
let after_comment_delete = Post::read(pool, inserted_post.id).await?;
assert_eq!(0, after_comment_delete.comments);
assert_eq!(0, after_comment_delete.score);
assert_eq!(1, after_comment_delete.upvotes);
assert_eq!(1, after_comment_delete.downvotes);
// Remove the first post like
PostLike::remove(pool, inserted_person.id, inserted_post.id).await?;
let after_like_remove = Post::read(pool, inserted_post.id).await?;
assert_eq!(0, after_like_remove.comments);
assert_eq!(-1, after_like_remove.score);
assert_eq!(0, after_like_remove.upvotes);
assert_eq!(1, after_like_remove.downvotes);
// This should delete all the associated rows, and fire triggers
Person::delete(pool, another_inserted_person.id).await?;
let person_num_deleted = Person::delete(pool, inserted_person.id).await?;
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(pool, inserted_community.id).await?;
assert_eq!(1, community_num_deleted);
// Should be none found, since the creator was deleted
let after_delete = Post::read(pool, inserted_post.id).await;
assert!(after_delete.is_err());
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_aggregates_soft_delete() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let new_person = PersonInsertForm::test_form(inserted_instance.id, "thommy_community_agg");
let inserted_person = Person::create(pool, &new_person).await?;
let new_community = CommunityInsertForm::new(
inserted_instance.id,
"TIL_community_agg".into(),
"nada".to_owned(),
"pubkey".to_string(),
);
let inserted_community = Community::create(pool, &new_community).await?;
let new_post = PostInsertForm::new(
"A test post".into(),
inserted_person.id,
inserted_community.id,
);
let inserted_post = Post::create(pool, &new_post).await?;
let comment_form = CommentInsertForm::new(
inserted_person.id,
inserted_post.id,
"A test comment".into(),
);
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let post_aggregates_before = Post::read(pool, inserted_post.id).await?;
assert_eq!(1, post_aggregates_before.comments);
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
let post_aggregates_after_remove = Post::read(pool, inserted_post.id).await?;
assert_eq!(0, post_aggregates_after_remove.comments);
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
removed: Some(false),
..Default::default()
},
)
.await?;
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
deleted: Some(true),
..Default::default()
},
)
.await?;
let post_aggregates_after_delete = Post::read(pool, inserted_post.id).await?;
assert_eq!(0, post_aggregates_after_delete.comments);
Comment::update(
pool,
inserted_comment.id,
&CommentUpdateForm {
removed: Some(true),
..Default::default()
},
)
.await?;
let post_aggregates_after_delete_remove = Post::read(pool, inserted_post.id).await?;
assert_eq!(0, post_aggregates_after_delete_remove.comments);
Comment::delete(pool, inserted_comment.id).await?;
Post::delete(pool, inserted_post.id).await?;
Person::delete(pool, inserted_person.id).await?;
Community::delete(pool, inserted_community.id).await?;
Instance::delete(pool, inserted_instance.id).await?;
Ok(())
}
}

View file

@ -1,8 +1,8 @@
use crate::{
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
diesel::OptionalExtension,
newtypes::{PersonId, PostId},
schema::post_actions,
source::post_actions::{PostActions, PostActionsForm},
utils::{get_conn, now, DbPool},
};
use diesel::{
@ -15,11 +15,8 @@ use diesel::{
};
use diesel_async::RunQueryDsl;
impl PersonPostAggregates {
pub async fn upsert(
pool: &mut DbPool<'_>,
form: &PersonPostAggregatesForm,
) -> Result<Self, Error> {
impl PostActions {
pub async fn upsert(pool: &mut DbPool<'_>, form: &PostActionsForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let form = (form, post_actions::read_comments.eq(now().nullable()));
insert_into(post_actions::table)

View file

@ -9,7 +9,6 @@ extern crate diesel_derive_newtype;
#[macro_use]
extern crate diesel_derive_enum;
pub mod aggregates;
#[cfg(feature = "full")]
pub mod impls;
pub mod newtypes;
@ -337,4 +336,8 @@ pub type Person1AliasAllColumnsTuple = (
AliasedField<aliases::Person1, person::bot_account>,
AliasedField<aliases::Person1, person::ban_expires>,
AliasedField<aliases::Person1, person::instance_id>,
AliasedField<aliases::Person1, person::post_count>,
AliasedField<aliases::Person1, person::post_score>,
AliasedField<aliases::Person1, person::comment_count>,
AliasedField<aliases::Person1, person::comment_score>,
);

View file

@ -130,6 +130,14 @@ diesel::table! {
path -> Ltree,
distinguished -> Bool,
language_id -> Int4,
score -> Int8,
upvotes -> Int8,
downvotes -> Int8,
child_count -> Int4,
hot_rank -> Float8,
controversy_rank -> Float8,
report_count -> Int2,
unresolved_report_count -> Int2,
}
}
@ -143,21 +151,6 @@ diesel::table! {
}
}
diesel::table! {
comment_aggregates (comment_id) {
comment_id -> Int4,
score -> Int8,
upvotes -> Int8,
downvotes -> Int8,
published -> Timestamptz,
child_count -> Int4,
hot_rank -> Float8,
controversy_rank -> Float8,
report_count -> Int2,
unresolved_report_count -> Int2,
}
}
diesel::table! {
comment_reply (id) {
id -> Int4,
@ -222,6 +215,18 @@ diesel::table! {
#[max_length = 150]
description -> Nullable<Varchar>,
random_number -> Int2,
subscribers -> Int8,
posts -> Int8,
comments -> Int8,
users_active_day -> Int8,
users_active_week -> Int8,
users_active_month -> Int8,
users_active_half_year -> Int8,
hot_rank -> Float8,
subscribers_local -> Int8,
report_count -> Int2,
unresolved_report_count -> Int2,
interactions_month -> Int8,
}
}
@ -242,25 +247,6 @@ diesel::table! {
}
}
diesel::table! {
community_aggregates (community_id) {
community_id -> Int4,
subscribers -> Int8,
posts -> Int8,
comments -> Int8,
published -> Timestamptz,
users_active_day -> Int8,
users_active_week -> Int8,
users_active_month -> Int8,
users_active_half_year -> Int8,
hot_rank -> Float8,
subscribers_local -> Int8,
report_count -> Int2,
unresolved_report_count -> Int2,
interactions_month -> Int8,
}
}
diesel::table! {
community_language (community_id, language_id) {
community_id -> Int4,
@ -449,6 +435,14 @@ diesel::table! {
comment_downvotes -> FederationModeEnum,
disable_donation_dialog -> Bool,
default_post_time_range_seconds -> Nullable<Int4>,
users -> Int8,
posts -> Int8,
comments -> Int8,
communities -> Int8,
users_active_day -> Int8,
users_active_week -> Int8,
users_active_month -> Int8,
users_active_half_year -> Int8,
disallow_nsfw_content -> Bool,
}
}
@ -524,6 +518,10 @@ diesel::table! {
last_donation_notification -> Timestamptz,
hide_media -> Bool,
default_post_time_range_seconds -> Nullable<Int4>,
show_score -> Bool,
show_upvotes -> Bool,
show_downvotes -> Bool,
show_upvote_percentage -> Bool,
}
}
@ -534,16 +532,6 @@ diesel::table! {
}
}
diesel::table! {
local_user_vote_display_mode (local_user_id) {
local_user_id -> Int4,
score -> Bool,
upvotes -> Bool,
downvotes -> Bool,
upvote_percentage -> Bool,
}
}
diesel::table! {
login_token (token) {
token -> Text,
@ -765,6 +753,10 @@ diesel::table! {
bot_account -> Bool,
ban_expires -> Nullable<Timestamptz>,
instance_id -> Int4,
post_count -> Int8,
post_score -> Int8,
comment_count -> Int8,
comment_score -> Int8,
}
}
@ -778,17 +770,6 @@ diesel::table! {
}
}
diesel::table! {
person_aggregates (person_id) {
person_id -> Int4,
post_count -> Int8,
post_score -> Int8,
comment_count -> Int8,
comment_score -> Int8,
published -> Timestamptz,
}
}
diesel::table! {
person_ban (person_id) {
person_id -> Int4,
@ -864,6 +845,19 @@ diesel::table! {
url_content_type -> Nullable<Text>,
alt_text -> Nullable<Text>,
scheduled_publish_time -> Nullable<Timestamptz>,
comments -> Int8,
score -> Int8,
upvotes -> Int8,
downvotes -> Int8,
newest_comment_time_necro -> Timestamptz,
newest_comment_time -> Timestamptz,
hot_rank -> Float8,
hot_rank_active -> Float8,
controversy_rank -> Float8,
instance_id -> Int4,
scaled_rank -> Float8,
report_count -> Int2,
unresolved_report_count -> Int2,
}
}
@ -881,30 +875,6 @@ diesel::table! {
}
}
diesel::table! {
post_aggregates (post_id) {
post_id -> Int4,
comments -> Int8,
score -> Int8,
upvotes -> Int8,
downvotes -> Int8,
published -> Timestamptz,
newest_comment_time_necro -> Timestamptz,
newest_comment_time -> Timestamptz,
featured_community -> Bool,
featured_local -> Bool,
hot_rank -> Float8,
hot_rank_active -> Float8,
community_id -> Int4,
creator_id -> Int4,
controversy_rank -> Float8,
instance_id -> Int4,
scaled_rank -> Float8,
report_count -> Int2,
unresolved_report_count -> Int2,
}
}
diesel::table! {
post_report (id) {
id -> Int4,
@ -1066,20 +1036,6 @@ diesel::table! {
}
}
diesel::table! {
site_aggregates (site_id) {
site_id -> Int4,
users -> Int8,
posts -> Int8,
comments -> Int8,
communities -> Int8,
users_active_day -> Int8,
users_active_week -> Int8,
users_active_month -> Int8,
users_active_half_year -> Int8,
}
}
diesel::table! {
site_language (site_id, language_id) {
site_id -> Int4,
@ -1123,13 +1079,11 @@ diesel::joinable!(comment -> person (creator_id));
diesel::joinable!(comment -> post (post_id));
diesel::joinable!(comment_actions -> comment (comment_id));
diesel::joinable!(comment_actions -> person (person_id));
diesel::joinable!(comment_aggregates -> comment (comment_id));
diesel::joinable!(comment_reply -> comment (comment_id));
diesel::joinable!(comment_reply -> person (recipient_id));
diesel::joinable!(comment_report -> comment (comment_id));
diesel::joinable!(community -> instance (instance_id));
diesel::joinable!(community_actions -> community (community_id));
diesel::joinable!(community_aggregates -> community (community_id));
diesel::joinable!(community_language -> community (community_id));
diesel::joinable!(community_language -> language (language_id));
diesel::joinable!(community_report -> community (community_id));
@ -1150,7 +1104,6 @@ diesel::joinable!(local_site_rate_limit -> local_site (local_site_id));
diesel::joinable!(local_user -> person (person_id));
diesel::joinable!(local_user_language -> language (language_id));
diesel::joinable!(local_user_language -> local_user (local_user_id));
diesel::joinable!(local_user_vote_display_mode -> local_user (local_user_id));
diesel::joinable!(login_token -> local_user (user_id));
diesel::joinable!(mod_add_community -> community (community_id));
diesel::joinable!(mod_ban_from_community -> community (community_id));
@ -1188,7 +1141,6 @@ diesel::joinable!(oauth_account -> local_user (local_user_id));
diesel::joinable!(oauth_account -> oauth_provider (oauth_provider_id));
diesel::joinable!(password_reset_request -> local_user (local_user_id));
diesel::joinable!(person -> instance (instance_id));
diesel::joinable!(person_aggregates -> person (person_id));
diesel::joinable!(person_ban -> person (person_id));
diesel::joinable!(person_comment_mention -> comment (comment_id));
diesel::joinable!(person_comment_mention -> person (recipient_id));
@ -1200,14 +1152,11 @@ diesel::joinable!(person_saved_combined -> comment (comment_id));
diesel::joinable!(person_saved_combined -> person (person_id));
diesel::joinable!(person_saved_combined -> post (post_id));
diesel::joinable!(post -> community (community_id));
diesel::joinable!(post -> instance (instance_id));
diesel::joinable!(post -> language (language_id));
diesel::joinable!(post -> person (creator_id));
diesel::joinable!(post_actions -> person (person_id));
diesel::joinable!(post_actions -> post (post_id));
diesel::joinable!(post_aggregates -> community (community_id));
diesel::joinable!(post_aggregates -> instance (instance_id));
diesel::joinable!(post_aggregates -> person (creator_id));
diesel::joinable!(post_aggregates -> post (post_id));
diesel::joinable!(post_report -> post (post_id));
diesel::joinable!(post_tag -> post (post_id));
diesel::joinable!(post_tag -> tag (tag_id));
@ -1223,7 +1172,6 @@ diesel::joinable!(search_combined -> community (community_id));
diesel::joinable!(search_combined -> person (person_id));
diesel::joinable!(search_combined -> post (post_id));
diesel::joinable!(site -> instance (instance_id));
diesel::joinable!(site_aggregates -> site (site_id));
diesel::joinable!(site_language -> language (language_id));
diesel::joinable!(site_language -> site (site_id));
diesel::joinable!(tag -> community (community_id));
@ -1238,12 +1186,10 @@ diesel::allow_tables_to_appear_in_same_query!(
captcha_answer,
comment,
comment_actions,
comment_aggregates,
comment_reply,
comment_report,
community,
community_actions,
community_aggregates,
community_language,
community_report,
custom_emoji,
@ -1263,7 +1209,6 @@ diesel::allow_tables_to_appear_in_same_query!(
local_site_url_blocklist,
local_user,
local_user_language,
local_user_vote_display_mode,
login_token,
mod_add,
mod_add_community,
@ -1282,7 +1227,6 @@ diesel::allow_tables_to_appear_in_same_query!(
password_reset_request,
person,
person_actions,
person_aggregates,
person_ban,
person_comment_mention,
person_content_combined,
@ -1290,7 +1234,6 @@ diesel::allow_tables_to_appear_in_same_query!(
person_saved_combined,
post,
post_actions,
post_aggregates,
post_report,
post_tag,
previously_run_sql,
@ -1304,7 +1247,6 @@ diesel::allow_tables_to_appear_in_same_query!(
secret,
sent_activity,
site,
site_aggregates,
site_language,
tag,
tagline,

View file

@ -14,7 +14,7 @@ use serde_with::skip_serializing_none;
use ts_rs::TS;
#[skip_serializing_none]
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
@ -51,6 +51,17 @@ pub struct Comment {
/// Whether the comment has been distinguished(speaking officially) by a mod.
pub distinguished: bool,
pub language_id: LanguageId,
pub score: i64,
pub upvotes: i64,
pub downvotes: i64,
/// The total number of children in this comment branch.
pub child_count: i32,
#[serde(skip)]
pub hot_rank: f64,
#[serde(skip)]
pub controversy_rank: f64,
pub report_count: i16,
pub unresolved_report_count: i16,
}
#[derive(Debug, Clone, derive_new::new)]

View file

@ -16,7 +16,7 @@ use strum::{Display, EnumString};
use ts_rs::TS;
#[skip_serializing_none]
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
#[cfg_attr(feature = "full", diesel(table_name = community))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
@ -78,6 +78,25 @@ pub struct Community {
pub description: Option<String>,
#[serde(skip)]
pub random_number: i16,
pub subscribers: i64,
pub posts: i64,
pub comments: i64,
/// The number of users with any activity in the last day.
pub users_active_day: i64,
/// The number of users with any activity in the last week.
pub users_active_week: i64,
/// The number of users with any activity in the last month.
pub users_active_month: i64,
/// The number of users with any activity in the last year.
pub users_active_half_year: i64,
#[serde(skip)]
pub hot_rank: f64,
pub subscribers_local: i64,
pub report_count: i16,
pub unresolved_report_count: i16,
/// Number of any interactions over the last month.
#[serde(skip)]
pub interactions_month: i64,
}
#[derive(Debug, Clone, derive_new::new)]

View file

@ -89,6 +89,18 @@ pub struct LocalSite {
#[cfg_attr(feature = "full", ts(optional))]
/// A default time range limit to apply to post sorts, in seconds.
pub default_post_time_range_seconds: Option<i32>,
pub users: i64,
pub posts: i64,
pub comments: i64,
pub communities: i64,
/// The number of users with any activity in the last day.
pub users_active_day: i64,
/// The number of users with any activity in the last week.
pub users_active_week: i64,
/// The number of users with any activity in the last month.
pub users_active_month: i64,
/// The number of users with any activity in the last half year.
pub users_active_half_year: i64,
/// Block NSFW content being created
pub disallow_nsfw_content: bool,
}

View file

@ -79,6 +79,10 @@ pub struct LocalUser {
#[cfg_attr(feature = "full", ts(optional))]
/// A default time range limit to apply to post sorts, in seconds.
pub default_post_time_range_seconds: Option<i32>,
pub show_score: bool,
pub show_upvotes: bool,
pub show_downvotes: bool,
pub show_upvote_percentage: bool,
}
#[derive(Clone, derive_new::new)]
@ -143,6 +147,14 @@ pub struct LocalUserInsertForm {
pub hide_media: Option<bool>,
#[new(default)]
pub default_post_time_range_seconds: Option<Option<i32>>,
#[new(default)]
pub show_score: Option<bool>,
#[new(default)]
pub show_upvotes: Option<bool>,
#[new(default)]
pub show_downvotes: Option<bool>,
#[new(default)]
pub show_upvote_percentage: Option<bool>,
}
#[derive(Clone, Default)]
@ -178,4 +190,8 @@ pub struct LocalUserUpdateForm {
pub last_donation_notification: Option<DateTime<Utc>>,
pub hide_media: Option<bool>,
pub default_post_time_range_seconds: Option<Option<i32>>,
pub show_score: Option<bool>,
pub show_upvotes: Option<bool>,
pub show_downvotes: Option<bool>,
pub show_upvote_percentage: Option<bool>,
}

View file

@ -1,53 +0,0 @@
use crate::newtypes::LocalUserId;
#[cfg(feature = "full")]
use crate::schema::local_user_vote_display_mode;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;
#[skip_serializing_none]
#[derive(PartialEq, Eq, Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
#[cfg_attr(feature = "full", diesel(table_name = local_user_vote_display_mode))]
#[cfg_attr(feature = "full", diesel(primary_key(local_user_id)))]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::local_site::LocalUser))
)]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// The vote display settings for your user.
pub struct LocalUserVoteDisplayMode {
#[serde(skip)]
pub local_user_id: LocalUserId,
pub score: bool,
pub upvotes: bool,
pub downvotes: bool,
pub upvote_percentage: bool,
}
#[derive(Clone, derive_new::new)]
#[cfg_attr(feature = "full", derive(Insertable))]
#[cfg_attr(feature = "full", diesel(table_name = local_user_vote_display_mode))]
pub struct LocalUserVoteDisplayModeInsertForm {
pub local_user_id: LocalUserId,
#[new(default)]
pub score: Option<bool>,
#[new(default)]
pub upvotes: Option<bool>,
#[new(default)]
pub downvotes: Option<bool>,
#[new(default)]
pub upvote_percentage: Option<bool>,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = local_user_vote_display_mode))]
pub struct LocalUserVoteDisplayModeUpdateForm {
pub score: Option<bool>,
pub upvotes: Option<bool>,
pub downvotes: Option<bool>,
pub upvote_percentage: Option<bool>,
}

View file

@ -26,7 +26,6 @@ pub mod local_site;
pub mod local_site_rate_limit;
pub mod local_site_url_blocklist;
pub mod local_user;
pub mod local_user_vote_display_mode;
pub mod login_token;
pub mod mod_log;
pub mod oauth_account;
@ -37,6 +36,7 @@ pub mod person_block;
pub mod person_comment_mention;
pub mod person_post_mention;
pub mod post;
pub mod post_actions;
pub mod post_report;
pub mod private_message;
pub mod private_message_report;

View file

@ -64,6 +64,12 @@ pub struct Person {
#[cfg_attr(feature = "full", ts(optional))]
pub ban_expires: Option<DateTime<Utc>>,
pub instance_id: InstanceId,
pub post_count: i64,
#[serde(skip)]
pub post_score: i64,
pub comment_count: i64,
#[serde(skip)]
pub comment_score: i64,
}
#[derive(Clone, derive_new::new)]

View file

@ -1,22 +1,25 @@
use crate::newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId};
#[cfg(feature = "full")]
use crate::schema::{post, post_actions};
use crate::newtypes::{CommunityId, DbUrl, InstanceId, LanguageId, PersonId, PostId};
use chrono::{DateTime, Utc};
#[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
#[cfg(feature = "full")]
use i_love_jesus::CursorKeysModule;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;
use {
crate::schema::{post, post_actions},
diesel::{dsl, expression_methods::NullableExpressionMethods},
i_love_jesus::CursorKeysModule,
ts_rs::TS,
};
#[skip_serializing_none]
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Identifiable, TS, CursorKeysModule)
)]
#[cfg_attr(feature = "full", diesel(table_name = post))]
#[cfg_attr(feature = "full", ts(export))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", cursor_keys_module(name = post_keys))]
/// A post.
pub struct Post {
pub id: PostId,
@ -69,6 +72,28 @@ pub struct Post {
/// Time at which the post will be published. None means publish immediately.
#[cfg_attr(feature = "full", ts(optional))]
pub scheduled_publish_time: Option<DateTime<Utc>>,
pub comments: i64,
pub score: i64,
pub upvotes: i64,
pub downvotes: i64,
#[serde(skip)]
/// A newest comment time, limited to 2 days, to prevent necrobumping
pub newest_comment_time_necro: DateTime<Utc>,
/// The time of the newest comment in the post.
pub newest_comment_time: DateTime<Utc>,
#[serde(skip)]
pub hot_rank: f64,
#[serde(skip)]
pub hot_rank_active: f64,
#[serde(skip)]
pub controversy_rank: f64,
#[serde(skip)]
pub instance_id: InstanceId,
/// A rank that amplifies smaller communities
#[serde(skip)]
pub scaled_rank: f64,
pub report_count: i16,
pub unresolved_report_count: i16,
}
#[derive(Debug, Clone, derive_new::new)]

View file

@ -0,0 +1,42 @@
use crate::newtypes::{PersonId, PostId};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[cfg(feature = "full")]
use {
crate::schema::post_actions,
diesel::{dsl, expression_methods::NullableExpressionMethods},
};
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable)
)]
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
/// Aggregate data for a person's post.
pub struct PostActions {
pub person_id: PersonId,
pub post_id: PostId,
/// The number of comments they've read on that post.
///
/// This is updated to the current post comment count every time they view a post.
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::read_comments_amount.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::read_comments_amount>))]
pub read_comments: i64,
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::read_comments.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::read_comments>))]
pub published: DateTime<Utc>,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = post_actions))]
pub struct PostActionsForm {
pub person_id: PersonId,
pub post_id: PostId,
#[cfg_attr(feature = "full", diesel(column_name = read_comments_amount))]
pub read_comments: i64,
}

View file

@ -25,7 +25,6 @@ use lemmy_db_schema::{
schema::{
comment,
comment_actions,
comment_aggregates,
comment_reply,
community,
community_actions,
@ -39,7 +38,6 @@ use lemmy_db_schema::{
person_post_mention,
post,
post_actions,
post_aggregates,
post_tag,
private_message,
tag,
@ -104,10 +102,6 @@ impl InboxCombinedViewInternal {
.and(creator_local_user.field(local_user::admin).eq(true)),
);
let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id));
let comment_aggregates_join =
comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id));
let image_details_join =
image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()));
@ -163,8 +157,6 @@ impl InboxCombinedViewInternal {
.inner_join(person::table.on(item_creator_join))
.inner_join(recipient_join)
.left_join(image_details_join)
.left_join(post_aggregates_join)
.left_join(comment_aggregates_join)
.left_join(creator_community_actions_join)
.left_join(local_user_join)
.left_join(creator_local_user_join)
@ -289,10 +281,9 @@ impl InboxCombinedQuery {
comment_reply::all_columns.nullable(),
person_comment_mention::all_columns.nullable(),
person_post_mention::all_columns.nullable(),
post_aggregates::all_columns.nullable(),
coalesce(
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments,
post::comments.nullable() - post_actions::read_comments_amount.nullable(),
post::comments,
)
.nullable(),
post_actions::saved.nullable(),
@ -306,7 +297,6 @@ impl InboxCombinedQuery {
post::all_columns.nullable(),
community::all_columns.nullable(),
comment::all_columns.nullable(),
comment_aggregates::all_columns.nullable(),
comment_actions::saved.nullable(),
comment_actions::like_score.nullable(),
community_follower_select_subscribed_type(),
@ -428,17 +418,15 @@ impl InternalToCombinedView for InboxCombinedViewInternal {
// Use for a short alias
let v = self;
if let (Some(comment_reply), Some(comment), Some(counts), Some(post), Some(community)) = (
if let (Some(comment_reply), Some(comment), Some(post), Some(community)) = (
v.comment_reply,
v.comment.clone(),
v.comment_counts.clone(),
v.post.clone(),
v.community.clone(),
) {
Some(InboxCombinedView::CommentReply(CommentReplyView {
comment_reply,
comment,
counts,
recipient: v.item_recipient,
post,
community,
@ -453,16 +441,9 @@ impl InternalToCombinedView for InboxCombinedViewInternal {
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
}))
} else if let (
Some(person_comment_mention),
Some(comment),
Some(counts),
Some(post),
Some(community),
) = (
} else if let (Some(person_comment_mention), Some(comment), Some(post), Some(community)) = (
v.person_comment_mention,
v.comment,
v.comment_counts,
v.post.clone(),
v.community.clone(),
) {
@ -470,7 +451,6 @@ impl InternalToCombinedView for InboxCombinedViewInternal {
PersonCommentMentionView {
person_comment_mention,
comment,
counts,
recipient: v.item_recipient,
post,
community,
@ -486,22 +466,14 @@ impl InternalToCombinedView for InboxCombinedViewInternal {
can_mod: v.can_mod,
},
))
} else if let (
Some(person_post_mention),
Some(post),
Some(counts),
Some(unread_comments),
Some(community),
) = (
} else if let (Some(person_post_mention), Some(post), Some(unread_comments), Some(community)) = (
v.person_post_mention,
v.post,
v.post_counts,
v.post_unread_comments,
v.community,
) {
Some(InboxCombinedView::PostMention(PersonPostMentionView {
person_post_mention,
counts,
post,
community,
recipient: v.item_recipient,

View file

@ -22,7 +22,6 @@ use lemmy_db_schema::{
schema::{
comment,
comment_actions,
comment_aggregates,
community,
community_actions,
image_details,
@ -32,7 +31,6 @@ use lemmy_db_schema::{
person_content_combined,
post,
post_actions,
post_aggregates,
post_tag,
tag,
},
@ -113,11 +111,6 @@ impl PersonContentCombinedViewInternal {
.and(comment_actions::person_id.nullable().eq(my_person_id)),
);
let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id));
let comment_aggregates_join = comment_aggregates::table
.on(person_content_combined::comment_id.eq(comment_aggregates::comment_id.nullable()));
let image_details_join =
image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()));
@ -132,8 +125,6 @@ impl PersonContentCombinedViewInternal {
.left_join(community_actions_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
.inner_join(post_aggregates_join)
.left_join(comment_aggregates_join)
.left_join(comment_actions_join)
.left_join(image_details_join)
}
@ -213,10 +204,9 @@ impl PersonContentCombinedQuery {
.filter(item_creator.eq(self.creator_id))
.select((
// Post-specific
post_aggregates::all_columns,
coalesce(
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments,
post::comments.nullable() - post_actions::read_comments_amount.nullable(),
post::comments,
),
post_actions::saved.nullable(),
post_actions::read.nullable().is_not_null(),
@ -226,7 +216,6 @@ impl PersonContentCombinedQuery {
post_tags,
// Comment-specific
comment::all_columns.nullable(),
comment_aggregates::all_columns.nullable(),
comment_actions::saved.nullable(),
comment_actions::like_score.nullable(),
// Shared
@ -297,10 +286,9 @@ impl InternalToCombinedView for PersonContentCombinedViewInternal {
// Use for a short alias
let v = self;
if let (Some(comment), Some(counts)) = (v.comment, v.comment_counts) {
if let Some(comment) = v.comment {
Some(PersonContentCombinedView::Comment(CommentView {
comment,
counts,
post: v.post,
community: v.community,
creator: v.item_creator,
@ -319,7 +307,6 @@ impl InternalToCombinedView for PersonContentCombinedViewInternal {
post: v.post,
community: v.community,
unread_comments: v.post_unread_comments,
counts: v.post_counts,
creator: v.item_creator,
creator_banned_from_community: v.item_creator_banned_from_community,
creator_is_moderator: v.item_creator_is_moderator,

View file

@ -22,7 +22,6 @@ use lemmy_db_schema::{
schema::{
comment,
comment_actions,
comment_aggregates,
community,
community_actions,
image_details,
@ -32,7 +31,6 @@ use lemmy_db_schema::{
person_saved_combined,
post,
post_actions,
post_aggregates,
post_tag,
tag,
},
@ -154,11 +152,6 @@ impl PersonSavedCombinedViewInternal {
.and(comment_actions::person_id.eq(my_person_id)),
);
let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id));
let comment_aggregates_join = comment_aggregates::table
.on(person_saved_combined::comment_id.eq(comment_aggregates::comment_id.nullable()));
let image_details_join =
image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()));
@ -173,8 +166,6 @@ impl PersonSavedCombinedViewInternal {
.left_join(community_actions_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
.inner_join(post_aggregates_join)
.left_join(comment_aggregates_join)
.left_join(comment_actions_join)
.left_join(image_details_join)
}
@ -203,10 +194,9 @@ impl PersonSavedCombinedQuery {
.filter(person_saved_combined::person_id.eq(my_person_id))
.select((
// Post-specific
post_aggregates::all_columns,
coalesce(
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments,
post::comments.nullable() - post_actions::read_comments_amount.nullable(),
post::comments,
),
post_actions::saved.nullable(),
post_actions::read.nullable().is_not_null(),
@ -216,7 +206,6 @@ impl PersonSavedCombinedQuery {
post_tags,
// Comment-specific
comment::all_columns.nullable(),
comment_aggregates::all_columns.nullable(),
comment_actions::saved.nullable(),
comment_actions::like_score.nullable(),
// Shared
@ -285,10 +274,9 @@ impl InternalToCombinedView for PersonSavedCombinedViewInternal {
// Use for a short alias
let v = self;
if let (Some(comment), Some(counts)) = (v.comment, v.comment_counts) {
if let Some(comment) = v.comment {
Some(PersonSavedCombinedView::Comment(CommentView {
comment,
counts,
post: v.post,
community: v.community,
creator: v.item_creator,
@ -307,7 +295,6 @@ impl InternalToCombinedView for PersonSavedCombinedViewInternal {
post: v.post,
community: v.community,
unread_comments: v.post_unread_comments,
counts: v.post_counts,
creator: v.item_creator,
creator_banned_from_community: v.item_creator_banned_from_community,
creator_is_moderator: v.item_creator_is_moderator,
@ -341,7 +328,6 @@ mod tests {
community::{Community, CommunityInsertForm},
instance::Instance,
local_user::{LocalUser, LocalUserInsertForm},
local_user_vote_display_mode::LocalUserVoteDisplayMode,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm, PostSaved, PostSavedForm},
},
@ -371,9 +357,7 @@ mod tests {
let timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
let timmy_view = LocalUserView {
local_user: timmy_local_user,
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
person: timmy.clone(),
counts: Default::default(),
};
let sara_form = PersonInsertForm::test_form(instance.id, "sara_pcv");

View file

@ -27,18 +27,15 @@ use lemmy_db_schema::{
schema::{
comment,
comment_actions,
comment_aggregates,
comment_report,
community,
community_actions,
community_aggregates,
community_report,
local_user,
person,
person_actions,
post,
post_actions,
post_aggregates,
post_report,
private_message,
private_message_report,
@ -138,15 +135,6 @@ impl ReportCombinedViewInternal {
.and(comment_actions::person_id.eq(my_person_id)),
);
let post_aggregates_join =
post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id));
let comment_aggregates_join =
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id));
let community_aggregates_join = community_aggregates::table
.on(community_report::community_id.eq(community_aggregates::community_id));
report_combined::table
.left_join(post_report::table)
.left_join(comment_report::table)
@ -164,9 +152,6 @@ impl ReportCombinedViewInternal {
.left_join(community_actions_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
.left_join(post_aggregates_join)
.left_join(comment_aggregates_join)
.left_join(community_aggregates_join)
.left_join(comment_actions_join)
}
@ -274,10 +259,9 @@ impl ReportCombinedQuery {
// Post-specific
post_report::all_columns.nullable(),
post::all_columns.nullable(),
post_aggregates::all_columns.nullable(),
coalesce(
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments,
post::comments.nullable() - post_actions::read_comments_amount.nullable(),
post::comments,
)
.nullable(),
post_actions::saved.nullable(),
@ -287,7 +271,6 @@ impl ReportCombinedQuery {
// Comment-specific
comment_report::all_columns.nullable(),
comment::all_columns.nullable(),
comment_aggregates::all_columns.nullable(),
comment_actions::saved.nullable(),
comment_actions::like_score.nullable(),
// Private-message-specific
@ -295,7 +278,6 @@ impl ReportCombinedQuery {
private_message::all_columns.nullable(),
// Community-specific
community_report::all_columns.nullable(),
community_aggregates::all_columns.nullable(),
// Shared
person::all_columns,
aliases::person1.fields(person::all_columns.nullable()),
@ -437,14 +419,12 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
Some(post),
Some(community),
Some(unread_comments),
Some(counts),
Some(post_creator),
) = (
v.post_report,
v.post.clone(),
v.community.clone(),
v.post_unread_comments,
v.post_counts,
v.item_creator.clone(),
) {
Some(ReportCombinedView::Post(PostReportView {
@ -452,7 +432,6 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
post,
community,
unread_comments,
counts,
creator: v.report_creator,
post_creator,
creator_banned_from_community: v.item_creator_banned_from_community,
@ -469,14 +448,12 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
} else if let (
Some(comment_report),
Some(comment),
Some(counts),
Some(post),
Some(community),
Some(comment_creator),
) = (
v.comment_report,
v.comment,
v.comment_counts,
v.post,
v.community.clone(),
v.item_creator.clone(),
@ -484,7 +461,6 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
Some(ReportCombinedView::Comment(CommentReportView {
comment_report,
comment,
counts,
post,
community,
creator: v.report_creator,
@ -513,14 +489,11 @@ impl InternalToCombinedView for ReportCombinedViewInternal {
resolver: v.resolver,
},
))
} else if let (Some(community), Some(community_report), Some(counts)) =
(v.community, v.community_report, v.community_counts)
{
} else if let (Some(community), Some(community_report)) = (v.community, v.community_report) {
Some(ReportCombinedView::Community(CommunityReportView {
community_report,
community,
creator: v.report_creator,
counts,
subscribed: v.subscribed,
resolver: v.resolver,
}))
@ -548,7 +521,6 @@ mod tests {
use diesel::{update, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
aggregates::structs::{CommentAggregates, PostAggregates},
assert_length,
schema::report_combined,
source::{
@ -558,7 +530,6 @@ mod tests {
community_report::{CommunityReport, CommunityReportForm},
instance::Instance,
local_user::{LocalUser, LocalUserInsertForm},
local_user_vote_display_mode::LocalUserVoteDisplayMode,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm},
post_report::{PostReport, PostReportForm},
@ -595,9 +566,7 @@ mod tests {
let timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
let timmy_view = LocalUserView {
local_user: timmy_local_user,
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
person: inserted_timmy.clone(),
counts: Default::default(),
};
// Make an admin, to be able to see private message reports.
@ -607,9 +576,7 @@ mod tests {
let admin_local_user = LocalUser::create(pool, &admin_local_user_form, vec![]).await?;
let admin_view = LocalUserView {
local_user: admin_local_user,
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
person: inserted_admin.clone(),
counts: Default::default(),
};
let sara_form = PersonInsertForm::test_form(inserted_instance.id, "sara_rcv");
@ -947,14 +914,14 @@ mod tests {
PostReportView::read(pool, inserted_jessica_report.id, data.timmy.id).await?;
// Make sure the triggers are reading the aggregates correctly.
let agg_1 = PostAggregates::read(pool, data.post.id).await?;
let agg_2 = PostAggregates::read(pool, data.post_2.id).await?;
let agg_1 = Post::read(pool, data.post.id).await?;
let agg_2 = Post::read(pool, data.post_2.id).await?;
assert_eq!(
read_jessica_report_view.post_report,
inserted_jessica_report
);
assert_eq!(read_jessica_report_view.post, data.post_2);
assert_eq!(read_jessica_report_view.post.id, data.post_2.id);
assert_eq!(read_jessica_report_view.community.id, data.community.id);
assert_eq!(read_jessica_report_view.creator.id, data.jessica.id);
assert_eq!(read_jessica_report_view.post_creator.id, data.timmy.id);
@ -1008,12 +975,12 @@ mod tests {
);
// Make sure the unresolved_post report got decremented in the trigger
let agg_2 = PostAggregates::read(pool, data.post_2.id).await?;
let agg_2 = Post::read(pool, data.post_2.id).await?;
assert_eq!(agg_2.report_count, 1);
assert_eq!(agg_2.unresolved_report_count, 0);
// Make sure the other unresolved report isn't changed
let agg_1 = PostAggregates::read(pool, data.post.id).await?;
let agg_1 = Post::read(pool, data.post.id).await?;
assert_eq!(agg_1.report_count, 1);
assert_eq!(agg_1.unresolved_report_count, 1);
@ -1072,12 +1039,12 @@ mod tests {
let inserted_jessica_report = CommentReport::report(pool, &jessica_report_form).await?;
let agg = CommentAggregates::read(pool, data.comment.id).await?;
assert_eq!(agg.report_count, 2);
let comment = Comment::read(pool, data.comment.id).await?;
assert_eq!(comment.report_count, 2);
let read_jessica_report_view =
CommentReportView::read(pool, inserted_jessica_report.id, data.timmy.id).await?;
assert_eq!(read_jessica_report_view.counts.unresolved_report_count, 2);
assert_eq!(read_jessica_report_view.comment.unresolved_report_count, 2);
// Do a batch read of timmys reports
let reports = ReportCombinedQuery::default()
@ -1340,7 +1307,7 @@ mod tests {
};
CommentReport::report(pool, &timmy_report_form).await?;
let agg = CommentAggregates::read(pool, data.comment.id).await?;
let agg = Comment::read(pool, data.comment.id).await?;
assert_eq!(agg.report_count, 2);
// Do a batch read of timmys reports, it should only show his own

View file

@ -26,18 +26,14 @@ use lemmy_db_schema::{
schema::{
comment,
comment_actions,
comment_aggregates,
community,
community_actions,
community_aggregates,
image_details,
local_user,
person,
person_actions,
person_aggregates,
post,
post_actions,
post_aggregates,
post_tag,
search_combined,
tag,
@ -147,20 +143,9 @@ impl SearchCombinedViewInternal {
.and(comment_actions::person_id.nullable().eq(my_person_id)),
);
let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id));
let comment_aggregates_join = comment_aggregates::table
.on(search_combined::comment_id.eq(comment_aggregates::comment_id.nullable()));
let community_aggregates_join = community_aggregates::table
.on(search_combined::community_id.eq(community_aggregates::community_id.nullable()));
let image_details_join =
image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()));
let person_aggregates_join = person_aggregates::table
.on(search_combined::person_id.eq(person_aggregates::person_id.nullable()));
search_combined::table
.left_join(comment_join)
.left_join(post_join)
@ -172,10 +157,6 @@ impl SearchCombinedViewInternal {
.left_join(community_actions_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
.left_join(person_aggregates_join)
.left_join(post_aggregates_join)
.left_join(comment_aggregates_join)
.left_join(community_aggregates_join)
.left_join(comment_actions_join)
.left_join(image_details_join)
}
@ -259,10 +240,9 @@ impl SearchCombinedQuery {
.select((
// Post-specific
post::all_columns.nullable(),
post_aggregates::all_columns.nullable(),
coalesce(
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments,
post::comments.nullable() - post_actions::read_comments_amount.nullable(),
post::comments,
)
.nullable(),
post_actions::saved.nullable(),
@ -273,16 +253,12 @@ impl SearchCombinedQuery {
post_tags,
// Comment-specific
comment::all_columns.nullable(),
comment_aggregates::all_columns.nullable(),
comment_actions::saved.nullable(),
comment_actions::like_score.nullable(),
// Community-specific
community::all_columns.nullable(),
community_aggregates::all_columns.nullable(),
community_actions::blocked.nullable().is_not_null(),
community_follower_select_subscribed_type(),
// Person
person_aggregates::all_columns.nullable(),
// // Shared
person::all_columns.nullable(),
local_user::admin.nullable().is_not_null(),
@ -440,16 +416,14 @@ impl InternalToCombinedView for SearchCombinedViewInternal {
// Use for a short alias
let v = self;
if let (Some(comment), Some(counts), Some(creator), Some(post), Some(community)) = (
if let (Some(comment), Some(creator), Some(post), Some(community)) = (
v.comment,
v.comment_counts,
v.item_creator.clone(),
v.post.clone(),
v.community.clone(),
) {
Some(SearchCombinedView::Comment(CommentView {
comment,
counts,
post,
community,
creator,
@ -463,15 +437,8 @@ impl InternalToCombinedView for SearchCombinedViewInternal {
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
}))
} else if let (
Some(post),
Some(counts),
Some(creator),
Some(community),
Some(unread_comments),
) = (
} else if let (Some(post), Some(creator), Some(community), Some(unread_comments)) = (
v.post,
v.post_counts,
v.item_creator.clone(),
v.community.clone(),
v.post_unread_comments,
@ -479,7 +446,6 @@ impl InternalToCombinedView for SearchCombinedViewInternal {
Some(SearchCombinedView::Post(PostView {
post,
community,
counts,
unread_comments,
creator,
creator_banned_from_community: v.item_creator_banned_from_community,
@ -496,19 +462,17 @@ impl InternalToCombinedView for SearchCombinedViewInternal {
tags: v.post_tags,
can_mod: v.can_mod,
}))
} else if let (Some(community), Some(counts)) = (v.community, v.community_counts) {
} else if let Some(community) = v.community {
Some(SearchCombinedView::Community(CommunityView {
community,
counts,
subscribed: v.subscribed,
blocked: v.community_blocked,
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
}))
} else if let (Some(person), Some(counts)) = (v.item_creator, v.item_creator_counts) {
} else if let Some(person) = v.item_creator {
Some(SearchCombinedView::Person(PersonView {
person,
counts,
is_admin: v.item_creator_is_admin,
}))
} else {
@ -532,7 +496,6 @@ mod tests {
community::{Community, CommunityInsertForm},
instance::Instance,
local_user::{LocalUser, LocalUserInsertForm},
local_user_vote_display_mode::LocalUserVoteDisplayMode,
person::{Person, PersonInsertForm},
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
},
@ -573,9 +536,7 @@ mod tests {
let timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
let timmy_view = LocalUserView {
local_user: timmy_local_user,
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
person: timmy.clone(),
counts: Default::default(),
};
let community_form = CommunityInsertForm {

View file

@ -21,7 +21,6 @@ use lemmy_db_schema::{
schema::{
comment,
comment_actions,
comment_aggregates,
community,
community_actions,
instance_actions,
@ -51,7 +50,7 @@ impl CommentView {
let comment_actions_join = comment_actions::table.on(
comment_actions::comment_id
.eq(comment_aggregates::comment_id)
.eq(comment::id)
.and(comment_actions::person_id.nullable().eq(my_person_id)),
);
@ -84,7 +83,6 @@ impl CommentView {
.inner_join(person::table)
.inner_join(post::table)
.inner_join(community_join)
.inner_join(comment_aggregates::table)
.left_join(community_actions_join)
.left_join(comment_actions_join)
.left_join(person_actions_join)
@ -134,7 +132,6 @@ impl CommentView {
CommentSlimView {
comment: self.comment,
creator: self.creator,
counts: self.counts,
creator_banned_from_community: self.creator_banned_from_community,
banned_from_community: self.banned_from_community,
creator_is_moderator: self.creator_is_moderator,
@ -296,21 +293,18 @@ impl CommentQuery<'_> {
query = match o.sort.unwrap_or(CommentSortType::Hot) {
CommentSortType::Hot => query
.then_order_by(comment_aggregates::hot_rank.desc())
.then_order_by(comment_aggregates::score.desc()),
CommentSortType::Controversial => {
query.then_order_by(comment_aggregates::controversy_rank.desc())
}
.then_order_by(comment::hot_rank.desc())
.then_order_by(comment::score.desc()),
CommentSortType::Controversial => query.then_order_by(comment::controversy_rank.desc()),
CommentSortType::New => query.then_order_by(comment::published.desc()),
CommentSortType::Old => query.then_order_by(comment::published.asc()),
CommentSortType::Top => query.then_order_by(comment_aggregates::score.desc()),
CommentSortType::Top => query.then_order_by(comment::score.desc()),
};
// Filter by the time range
if let Some(time_range_seconds) = o.time_range_seconds {
query = query.filter(
comment_aggregates::published.gt(now() - seconds_to_pg_interval(time_range_seconds)),
);
query =
query.filter(comment::published.gt(now() - seconds_to_pg_interval(time_range_seconds)));
}
let res = query
@ -332,10 +326,9 @@ mod tests {
structs::LocalUserView,
};
use lemmy_db_schema::{
aggregates::structs::CommentAggregates,
assert_length,
impls::actor_language::UNDETERMINED_ID,
newtypes::LanguageId,
newtypes::{CommentId, LanguageId},
source::{
actor_language::LocalUserLanguage,
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
@ -354,7 +347,6 @@ mod tests {
instance::Instance,
language::Language,
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
local_user_vote_display_mode::LocalUserVoteDisplayMode,
person::{Person, PersonInsertForm},
person_block::{PersonBlock, PersonBlockForm},
post::{Post, PostInsertForm, PostUpdateForm},
@ -374,6 +366,7 @@ mod tests {
inserted_comment_0: Comment,
inserted_comment_1: Comment,
inserted_comment_2: Comment,
inserted_comment_5: Comment,
inserted_post: Post,
timmy_local_user_view: LocalUserView,
inserted_sara_person: Person,
@ -481,7 +474,7 @@ mod tests {
inserted_post.id,
"Comment 5".into(),
);
let _inserted_comment_5 =
let inserted_comment_5 =
Comment::create(pool, &comment_form_5, Some(&inserted_comment_4.path)).await?;
let timmy_blocks_sara_form = PersonBlockForm {
@ -508,9 +501,7 @@ mod tests {
let timmy_local_user_view = LocalUserView {
local_user: inserted_timmy_local_user.clone(),
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
person: inserted_timmy_person.clone(),
counts: Default::default(),
};
let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id);
let site = Site::create(pool, &site_form).await?;
@ -519,6 +510,7 @@ mod tests {
inserted_comment_0,
inserted_comment_1,
inserted_comment_2,
inserted_comment_5,
inserted_post,
timmy_local_user_view,
inserted_sara_person,
@ -534,7 +526,7 @@ mod tests {
let pool = &mut pool.into();
let data = init_data(pool).await?;
let expected_comment_view_no_person = expected_comment_view(&data, pool).await?;
let expected_comment_view_no_person = expected_comment_view(&data);
let mut expected_comment_view_with_person = expected_comment_view_no_person.clone();
expected_comment_view_with_person.my_vote = Some(1);
@ -666,10 +658,10 @@ mod tests {
// 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(&data.inserted_comment_1));
assert!(!child_comments.contains(&data.inserted_comment_2));
.map(|c| c.comment.id)
.collect::<Vec<CommentId>>();
assert!(child_comments.contains(&data.inserted_comment_1.id));
assert!(!child_comments.contains(&data.inserted_comment_2.id));
let read_comment_views_top_max_depth = CommentQuery {
post_id: (Some(data.inserted_post.id)),
@ -681,7 +673,7 @@ mod tests {
// Make sure a depth limited one only has the top comment
assert_eq!(
expected_comment_view(&data, pool).await?,
expected_comment_view(&data),
read_comment_views_top_max_depth[0]
);
assert_length!(1, read_comment_views_top_max_depth);
@ -867,9 +859,8 @@ mod tests {
Ok(())
}
async fn expected_comment_view(data: &Data, pool: &mut DbPool<'_>) -> LemmyResult<CommentView> {
let agg = CommentAggregates::read(pool, data.inserted_comment_0.id).await?;
Ok(CommentView {
fn expected_comment_view(data: &Data) -> CommentView {
CommentView {
creator_banned_from_community: false,
banned_from_community: false,
creator_is_moderator: false,
@ -893,6 +884,14 @@ mod tests {
distinguished: false,
path: data.inserted_comment_0.clone().path,
language_id: LanguageId(37),
score: 1,
upvotes: 1,
downvotes: 0,
child_count: 5,
hot_rank: RANK_DEFAULT,
controversy_rank: 0.0,
report_count: 0,
unresolved_report_count: 0,
},
creator: Person {
id: data.timmy_local_user_view.person.id,
@ -915,6 +914,10 @@ mod tests {
private_key: data.timmy_local_user_view.person.private_key.clone(),
public_key: data.timmy_local_user_view.person.public_key.clone(),
last_refreshed_at: data.timmy_local_user_view.person.last_refreshed_at,
post_count: 1,
post_score: 0,
comment_count: 5,
comment_score: 1,
},
post: Post {
id: data.inserted_post.id,
@ -941,6 +944,19 @@ mod tests {
featured_local: false,
url_content_type: None,
scheduled_publish_time: None,
comments: 6,
score: 0,
upvotes: 0,
downvotes: 0,
newest_comment_time_necro: data.inserted_comment_1.published,
newest_comment_time: data.inserted_comment_5.published,
hot_rank: RANK_DEFAULT,
hot_rank_active: RANK_DEFAULT,
controversy_rank: 0.0,
scaled_rank: RANK_DEFAULT,
instance_id: data.inserted_instance.id,
report_count: 0,
unresolved_report_count: 0,
},
community: Community {
id: data.inserted_community.id,
@ -969,20 +985,20 @@ mod tests {
featured_url: data.inserted_community.featured_url.clone(),
visibility: CommunityVisibility::Public,
random_number: data.inserted_community.random_number,
},
counts: CommentAggregates {
comment_id: data.inserted_comment_0.id,
score: 1,
upvotes: 1,
downvotes: 0,
published: agg.published,
child_count: 5,
subscribers: 0,
posts: 1,
comments: 6,
users_active_day: 0,
users_active_week: 0,
users_active_month: 0,
users_active_half_year: 0,
hot_rank: RANK_DEFAULT,
controversy_rank: 0.0,
subscribers_local: 0,
report_count: 0,
unresolved_report_count: 0,
interactions_month: 0,
},
})
}
}
#[tokio::test]

View file

@ -12,7 +12,7 @@ use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
impls::local_user::LocalUserOptionHelper,
newtypes::{CommunityId, PersonId},
schema::{community, community_actions, community_aggregates, instance_actions, local_user},
schema::{community, community_actions, instance_actions, local_user},
source::{
community::{Community, CommunityFollowerState},
local_user::LocalUser,
@ -41,7 +41,6 @@ impl CommunityView {
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(person_id));
community::table
.inner_join(community_aggregates::table)
.left_join(community_actions_join)
.left_join(instance_actions_join)
.left_join(local_user_join)
@ -164,27 +163,24 @@ impl CommunityQuery<'_> {
query = o.local_user.visible_communities_only(query);
match o.sort.unwrap_or_default() {
Hot => query = query.order_by(community_aggregates::hot_rank.desc()),
Comments => query = query.order_by(community_aggregates::comments.desc()),
Posts => query = query.order_by(community_aggregates::posts.desc()),
Hot => query = query.order_by(community::hot_rank.desc()),
Comments => query = query.order_by(community::comments.desc()),
Posts => query = query.order_by(community::posts.desc()),
New => query = query.order_by(community::published.desc()),
Old => query = query.order_by(community::published.asc()),
Subscribers => query = query.order_by(community_aggregates::subscribers.desc()),
SubscribersLocal => query = query.order_by(community_aggregates::subscribers_local.desc()),
ActiveSixMonths => {
query = query.order_by(community_aggregates::users_active_half_year.desc())
}
ActiveMonthly => query = query.order_by(community_aggregates::users_active_month.desc()),
ActiveWeekly => query = query.order_by(community_aggregates::users_active_week.desc()),
ActiveDaily => query = query.order_by(community_aggregates::users_active_day.desc()),
Subscribers => query = query.order_by(community::subscribers.desc()),
SubscribersLocal => query = query.order_by(community::subscribers_local.desc()),
ActiveSixMonths => query = query.order_by(community::users_active_half_year.desc()),
ActiveMonthly => query = query.order_by(community::users_active_month.desc()),
ActiveWeekly => query = query.order_by(community::users_active_week.desc()),
ActiveDaily => query = query.order_by(community::users_active_day.desc()),
NameAsc => query = query.order_by(lower(community::name).asc()),
NameDesc => query = query.order_by(lower(community::name).desc()),
};
// Filter by the time range
if let Some(time_range_seconds) = o.time_range_seconds {
query = query.filter(
community_aggregates::published.gt(now() - seconds_to_pg_interval(time_range_seconds)),
);
query =
query.filter(community::published.gt(now() - seconds_to_pg_interval(time_range_seconds)));
}
let (limit, offset) = limit_and_offset(o.page, o.limit)?;

View file

@ -1,17 +1,10 @@
use crate::structs::LocalUserView;
use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest};
use diesel::{
result::Error,
BoolExpressionMethods,
ExpressionMethods,
JoinOnDsl,
QueryDsl,
SelectableHelper,
};
use diesel::{result::Error, BoolExpressionMethods, ExpressionMethods, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
newtypes::{LocalUserId, OAuthProviderId, PersonId},
schema::{local_user, local_user_vote_display_mode, oauth_account, person, person_aggregates},
schema::{local_user, oauth_account, person},
source::{
instance::Instance,
local_user::{LocalUser, LocalUserInsertForm},
@ -30,10 +23,7 @@ use std::future::{ready, Ready};
impl LocalUserView {
#[diesel::dsl::auto_type(no_type_alias)]
fn joins() -> _ {
local_user::table
.inner_join(local_user_vote_display_mode::table)
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
local_user::table.inner_join(person::table)
}
pub async fn read(pool: &mut DbPool<'_>, local_user_id: LocalUserId) -> Result<Self, Error> {

View file

@ -10,16 +10,14 @@ use diesel::{
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
newtypes::PersonId,
schema::{local_user, person, person_aggregates},
schema::{local_user, person},
utils::{get_conn, now, DbPool},
};
impl PersonView {
#[diesel::dsl::auto_type(no_type_alias)]
fn joins() -> _ {
person::table
.inner_join(person_aggregates::table)
.left_join(local_user::table)
person::table.left_join(local_user::table)
}
pub async fn read(

View file

@ -19,7 +19,6 @@ use diesel::{
};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
aggregates::structs::{post_aggregates_keys as key, PostAggregates},
aliases::{creator_community_actions, creator_local_user},
impls::{
community::community_follower_select_subscribed_type,
@ -37,16 +36,16 @@ use lemmy_db_schema::{
person_actions,
post,
post_actions,
post_aggregates,
post_tag,
tag,
},
source::{
community::CommunityFollowerState,
local_user::LocalUser,
post::{post_actions_keys, PostActionsCursor},
post::{post_actions_keys, post_keys as key, Post, PostActionsCursor},
site::Site,
},
traits::Crud,
utils::{
functions::coalesce,
fuzzy_search,
@ -75,36 +74,36 @@ impl PostView {
fn joins(my_person_id: Option<PersonId>) -> _ {
let community_actions_join = community_actions::table.on(
community_actions::community_id
.eq(post_aggregates::community_id)
.eq(post::community_id)
.and(community_actions::person_id.nullable().eq(my_person_id)),
);
let person_actions_join = person_actions::table.on(
person_actions::target_id
.eq(post_aggregates::creator_id)
.eq(post::creator_id)
.and(person_actions::person_id.nullable().eq(my_person_id)),
);
let post_actions_join = post_actions::table.on(
post_actions::post_id
.eq(post_aggregates::post_id)
.eq(post::id)
.and(post_actions::person_id.nullable().eq(my_person_id)),
);
let instance_actions_join = instance_actions::table.on(
instance_actions::instance_id
.eq(post_aggregates::instance_id)
.eq(post::instance_id)
.and(instance_actions::person_id.nullable().eq(my_person_id)),
);
let post_creator_community_actions_join = creator_community_actions.on(
creator_community_actions
.field(community_actions::community_id)
.eq(post_aggregates::community_id)
.eq(post::community_id)
.and(
creator_community_actions
.field(community_actions::person_id)
.eq(post_aggregates::creator_id),
.eq(post::creator_id),
),
);
@ -113,10 +112,9 @@ impl PostView {
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(my_person_id));
post_aggregates::table
post::table
.inner_join(person::table)
.inner_join(community::table)
.inner_join(post::table)
.left_join(image_details_join)
.left_join(community_actions_join)
.left_join(person_actions_join)
@ -130,7 +128,7 @@ impl PostView {
fn creator_is_admin() -> _ {
exists(
creator_local_user.filter(
post_aggregates::creator_id
post::creator_id
.eq(creator_local_user.field(local_user::person_id))
.and(creator_local_user.field(local_user::admin).eq(true)),
),
@ -151,12 +149,12 @@ impl PostView {
.select(diesel::dsl::sql::<diesel::sql_types::Json>(
"json_agg(tag.*)",
))
.filter(post_tag::post_id.eq(post_aggregates::post_id))
.filter(post_tag::post_id.eq(post::id))
.filter(tag::deleted.eq(false))
.single_value();
let mut query = Self::joins(my_person_id)
.filter(post_aggregates::post_id.eq(post_id))
.filter(post::id.eq(post_id))
.select((
post::all_columns,
person::all_columns,
@ -172,7 +170,6 @@ impl PostView {
.nullable()
.is_not_null(),
Self::creator_is_admin(),
post_aggregates::all_columns,
community_follower_select_subscribed_type(),
post_actions::saved.nullable(),
post_actions::read.nullable().is_not_null(),
@ -180,8 +177,8 @@ impl PostView {
person_actions::blocked.nullable().is_not_null(),
post_actions::like_score.nullable(),
coalesce(
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments,
post::comments.nullable() - post_actions::read_comments_amount.nullable(),
post::comments,
),
post_tags,
local_user_can_mod(),
@ -234,7 +231,7 @@ impl PostPaginationCursor {
// get cursor for page that starts immediately after the given post
pub fn after_post(view: &PostView) -> PostPaginationCursor {
// hex encoding to prevent ossification
PostPaginationCursor(format!("P{:x}", view.counts.post_id.0))
PostPaginationCursor(format!("P{:x}", view.post.id.0))
}
pub async fn read(
&self,
@ -249,13 +246,10 @@ impl PostPaginationCursor {
.and_then(|e| i32::from_str_radix(e, 16).ok())
.ok_or_else(err_msg)?,
);
let post_aggregates = PostAggregates::read(pool, post_id).await?;
let post = Post::read(pool, post_id).await?;
let post_actions = PostActionsCursor::read(pool, post_id, local_user.person_id()).await?;
Ok(PaginationCursorData {
post_aggregates,
post_actions,
})
Ok(PaginationCursorData { post, post_actions })
}
}
@ -263,7 +257,7 @@ impl PostPaginationCursor {
// we only use some of the properties, depending on which sort type we page by
#[derive(Clone)]
pub struct PaginationCursorData {
post_aggregates: PostAggregates,
post: Post,
post_actions: PostActionsCursor,
}
@ -289,7 +283,7 @@ pub struct PostQuery<'a> {
pub limit: Option<i64>,
// TODO these should be simple cursors like the others, not data
pub page_after: Option<PaginationCursorData>,
pub page_before_or_equal: Option<PostAggregates>,
pub page_before_or_equal: Option<Post>,
pub page_back: Option<bool>,
pub show_hidden: Option<bool>,
pub show_read: Option<bool>,
@ -317,11 +311,6 @@ impl<'a> PostQuery<'a> {
// covers the "worst case" of the whole page consisting of posts from one community
// but using the largest community decreases the pagination-frame so make the real query more
// efficient.
use lemmy_db_schema::schema::community_aggregates::dsl::{
community_aggregates,
community_id,
users_active_month,
};
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
if offset != 0 && self.page_after.is_some() {
return Err(Error::QueryBuilderError(
@ -334,9 +323,9 @@ impl<'a> PostQuery<'a> {
community_actions::table
.filter(community_actions::followed.is_not_null())
.filter(community_actions::person_id.eq(self_person_id))
.inner_join(community_aggregates.on(community_id.eq(community_actions::community_id)))
.order_by(users_active_month.desc())
.select(community_id)
.inner_join(community::table.on(community::id.eq(community_actions::community_id)))
.order_by(community::users_active_month.desc())
.select(community::id)
.limit(1)
.get_result::<CommunityId>(conn)
.await
@ -369,7 +358,7 @@ impl<'a> PostQuery<'a> {
} else {
v.pop()
};
let limit_cursor = Some(item.expect("else case").counts);
let limit_cursor = Some(item.expect("else case").post);
Ok(Some(PostQuery {
page_before_or_equal: limit_cursor,
..self.clone()
@ -405,7 +394,7 @@ impl<'a> PostQuery<'a> {
.select(diesel::dsl::sql::<diesel::sql_types::Json>(
"json_agg(tag.*)",
))
.filter(post_tag::post_id.eq(post_aggregates::post_id))
.filter(post_tag::post_id.eq(post::id))
.filter(tag::deleted.eq(false))
.single_value();
@ -425,7 +414,6 @@ impl<'a> PostQuery<'a> {
.nullable()
.is_not_null(),
PostView::creator_is_admin(),
post_aggregates::all_columns,
community_follower_select_subscribed_type(),
post_actions::saved.nullable(),
post_actions::read.nullable().is_not_null(),
@ -433,8 +421,8 @@ impl<'a> PostQuery<'a> {
person_actions::blocked.nullable().is_not_null(),
post_actions::like_score.nullable(),
coalesce(
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments,
post::comments.nullable() - post_actions::read_comments_amount.nullable(),
post::comments,
),
post_tags,
local_user_can_mod(),
@ -465,11 +453,11 @@ impl<'a> PostQuery<'a> {
.filter(post::removed.eq(false));
}
if let Some(community_id) = o.community_id {
query = query.filter(post_aggregates::community_id.eq(community_id));
query = query.filter(post::community_id.eq(community_id));
}
if let Some(creator_id) = o.creator_id {
query = query.filter(post_aggregates::creator_id.eq(creator_id));
query = query.filter(post::creator_id.eq(creator_id));
}
let is_subscribed = community_actions::followed.is_not_null();
@ -521,7 +509,7 @@ impl<'a> PostQuery<'a> {
// Filter to show only posts with no comments
if o.no_comments_only.unwrap_or_default() {
query = query.filter(post_aggregates::comments.eq(0));
query = query.filter(post::comments.eq(0));
};
if !o.show_read.unwrap_or(o.local_user.show_read_posts()) {
@ -548,7 +536,7 @@ impl<'a> PostQuery<'a> {
}
if let Some(my_id) = o.local_user.person_id() {
let not_creator_filter = post_aggregates::creator_id.ne(my_id);
let not_creator_filter = post::creator_id.ne(my_id);
if o.liked_only.unwrap_or_default() {
query = query
.filter(not_creator_filter)
@ -604,7 +592,7 @@ impl<'a> PostQuery<'a> {
} else {
let mut query = paginate(
query,
o.page_after.map(|c| c.post_aggregates),
o.page_after.map(|c| c.post),
o.page_before_or_equal,
o.page_back.unwrap_or_default(),
);
@ -631,9 +619,8 @@ impl<'a> PostQuery<'a> {
// Filter by the time range
if let Some(time_range_seconds) = o.time_range_seconds {
query = query.filter(
post_aggregates::published.gt(now() - seconds_to_pg_interval(time_range_seconds)),
);
query =
query.filter(post::published.gt(now() - seconds_to_pg_interval(time_range_seconds)));
}
// use publish as fallback. especially useful for hot rank which reaches zero after some days.
@ -645,7 +632,7 @@ impl<'a> PostQuery<'a> {
};
// finally use unique post id as tie breaker
query = query.then_desc(key::post_id);
query = query.then_desc(key::id);
query.as_query()
};
@ -674,7 +661,6 @@ mod tests {
use chrono::Utc;
use diesel_async::SimpleAsyncConnection;
use lemmy_db_schema::{
aggregates::structs::PostAggregates,
impls::actor_language::UNDETERMINED_ID,
newtypes::LanguageId,
source::{
@ -697,7 +683,6 @@ mod tests {
instance_block::{InstanceBlock, InstanceBlockForm},
language::Language,
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
local_user_vote_display_mode::LocalUserVoteDisplayMode,
person::{Person, PersonInsertForm},
person_block::{PersonBlock, PersonBlockForm},
post::{
@ -894,22 +879,16 @@ mod tests {
let tegan_local_user_view = LocalUserView {
local_user: inserted_tegan_local_user,
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
person: inserted_tegan_person,
counts: Default::default(),
};
let john_local_user_view = LocalUserView {
local_user: inserted_john_local_user,
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
person: inserted_john_person,
counts: Default::default(),
};
let bot_local_user_view = LocalUserView {
local_user: inserted_bot_local_user,
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
person: inserted_bot_person,
counts: Default::default(),
};
let site = Site {
@ -1004,7 +983,7 @@ mod tests {
)
.await?;
let expected_post_listing_with_user = expected_post_view(data, pool).await?;
let expected_post_listing_with_user = expected_post_view(data)?;
// Should be only one person, IE the bot post, and blocked should be missing
assert_eq!(
@ -1060,7 +1039,7 @@ mod tests {
let read_post_listing_single_no_person =
PostView::read(pool, data.post.id, None, false).await?;
let mut expected_post_listing_no_person = expected_post_view(data, pool).await?;
let mut expected_post_listing_no_person = expected_post_view(data)?;
expected_post_listing_no_person.can_mod = false;
// Should be 2 posts, with the bot post, and the blocked
@ -1193,10 +1172,11 @@ mod tests {
)
.await?;
let mut expected_post_with_upvote = expected_post_view(data, pool).await?;
let mut expected_post_with_upvote = expected_post_view(data)?;
expected_post_with_upvote.my_vote = Some(1);
expected_post_with_upvote.counts.score = 1;
expected_post_with_upvote.counts.upvotes = 1;
expected_post_with_upvote.post.score = 1;
expected_post_with_upvote.post.upvotes = 1;
expected_post_with_upvote.creator.post_score = 1;
assert_eq!(expected_post_with_upvote, post_listing_single_with_person);
let local_user_form = LocalUserUpdateForm {
@ -1745,7 +1725,7 @@ mod tests {
loop {
let post_listings = PostQuery {
page_after: page_after.map(|p| PaginationCursorData {
post_aggregates: p,
post: p,
post_actions: Default::default(),
}),
..options.clone()
@ -1756,7 +1736,7 @@ mod tests {
listed_post_ids.extend(post_listings.iter().map(|p| p.post.id));
if let Some(p) = post_listings.into_iter().next_back() {
page_after = Some(p.counts);
page_after = Some(p.post);
} else {
break;
}
@ -1768,7 +1748,7 @@ mod tests {
loop {
let post_listings = PostQuery {
page_after: page_before.map(|p| PaginationCursorData {
post_aggregates: p,
post: p,
post_actions: Default::default(),
}),
page_back: Some(true),
@ -1787,7 +1767,7 @@ mod tests {
listed_post_ids_forward.truncate(index);
if let Some(p) = post_listings.into_iter().next() {
page_before = Some(p.counts);
page_before = Some(p.post);
} else {
break;
}
@ -1938,13 +1918,12 @@ mod tests {
Ok(())
}
async fn expected_post_view(data: &Data, pool: &mut DbPool<'_>) -> LemmyResult<PostView> {
fn expected_post_view(data: &Data) -> LemmyResult<PostView> {
let (inserted_person, inserted_community, inserted_post) = (
&data.tegan_local_user_view.person,
&data.community,
&data.post,
);
let agg = PostAggregates::read(pool, inserted_post.id).await?;
Ok(PostView {
post: Post {
@ -1972,6 +1951,19 @@ mod tests {
featured_local: false,
url_content_type: None,
scheduled_publish_time: None,
comments: 0,
score: 0,
upvotes: 0,
downvotes: 0,
newest_comment_time_necro: inserted_post.published,
newest_comment_time: inserted_post.published,
hot_rank: RANK_DEFAULT,
hot_rank_active: RANK_DEFAULT,
controversy_rank: 0.0,
scaled_rank: RANK_DEFAULT,
instance_id: data.instance.id,
report_count: 0,
unresolved_report_count: 0,
},
my_vote: None,
unread_comments: 0,
@ -1996,6 +1988,10 @@ mod tests {
private_key: inserted_person.private_key.clone(),
public_key: inserted_person.public_key.clone(),
last_refreshed_at: inserted_person.last_refreshed_at,
post_count: 2,
post_score: 0,
comment_count: 0,
comment_score: 0,
},
image_details: None,
creator_banned_from_community: false,
@ -2030,27 +2026,18 @@ mod tests {
featured_url: inserted_community.featured_url.clone(),
visibility: CommunityVisibility::Public,
random_number: inserted_community.random_number,
},
counts: PostAggregates {
post_id: inserted_post.id,
subscribers: 0,
posts: 4,
comments: 0,
score: 0,
upvotes: 0,
downvotes: 0,
published: agg.published,
newest_comment_time_necro: inserted_post.published,
newest_comment_time: inserted_post.published,
featured_community: false,
featured_local: false,
users_active_day: 0,
users_active_week: 0,
users_active_month: 0,
users_active_half_year: 0,
hot_rank: RANK_DEFAULT,
hot_rank_active: RANK_DEFAULT,
controversy_rank: 0.0,
scaled_rank: RANK_DEFAULT,
community_id: inserted_post.community_id,
creator_id: inserted_post.creator_id,
instance_id: data.instance.id,
subscribers_local: 0,
report_count: 0,
unresolved_report_count: 0,
interactions_month: 0,
},
subscribed: SubscribedType::NotSubscribed,
read: false,

View file

@ -140,28 +140,28 @@ mod tests {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
let timmy_person_form = PersonInsertForm::test_form(inserted_instance.id, "timmy_rav");
let timmy_person_form = PersonInsertForm::test_form(instance.id, "timmy_rav");
let inserted_timmy_person = Person::create(pool, &timmy_person_form).await?;
let timmy_person = Person::create(pool, &timmy_person_form).await?;
let timmy_local_user_form = LocalUserInsertForm::test_form_admin(inserted_timmy_person.id);
let timmy_local_user_form = LocalUserInsertForm::test_form_admin(timmy_person.id);
let _inserted_timmy_local_user =
LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
let sara_person_form = PersonInsertForm::test_form(inserted_instance.id, "sara_rav");
let sara_person_form = PersonInsertForm::test_form(instance.id, "sara_rav");
let inserted_sara_person = Person::create(pool, &sara_person_form).await?;
let sara_person = Person::create(pool, &sara_person_form).await?;
let sara_local_user_form = LocalUserInsertForm::test_form(inserted_sara_person.id);
let sara_local_user_form = LocalUserInsertForm::test_form(sara_person.id);
let inserted_sara_local_user = LocalUser::create(pool, &sara_local_user_form, vec![]).await?;
let sara_local_user = LocalUser::create(pool, &sara_local_user_form, vec![]).await?;
// Sara creates an application
let sara_app_form = RegistrationApplicationInsertForm {
local_user_id: inserted_sara_local_user.id,
local_user_id: sara_local_user.id,
answer: "LET ME IIIIINN".to_string(),
};
@ -169,17 +169,17 @@ mod tests {
let read_sara_app_view = RegistrationApplicationView::read(pool, sara_app.id).await?;
let jess_person_form = PersonInsertForm::test_form(inserted_instance.id, "jess_rav");
let jess_person_form = PersonInsertForm::test_form(instance.id, "jess_rav");
let inserted_jess_person = Person::create(pool, &jess_person_form).await?;
let jess_local_user_form = LocalUserInsertForm::test_form(inserted_jess_person.id);
let inserted_jess_local_user = LocalUser::create(pool, &jess_local_user_form, vec![]).await?;
let jess_local_user = LocalUser::create(pool, &jess_local_user_form, vec![]).await?;
// Sara creates an application
let jess_app_form = RegistrationApplicationInsertForm {
local_user_id: inserted_jess_local_user.id,
local_user_id: jess_local_user.id,
answer: "LET ME IIIIINN".to_string(),
};
@ -190,42 +190,44 @@ mod tests {
let mut expected_sara_app_view = RegistrationApplicationView {
registration_application: sara_app.clone(),
creator_local_user: LocalUser {
id: inserted_sara_local_user.id,
person_id: inserted_sara_local_user.person_id,
email: inserted_sara_local_user.email,
show_nsfw: inserted_sara_local_user.show_nsfw,
blur_nsfw: inserted_sara_local_user.blur_nsfw,
theme: inserted_sara_local_user.theme,
default_post_sort_type: inserted_sara_local_user.default_post_sort_type,
default_comment_sort_type: inserted_sara_local_user.default_comment_sort_type,
default_listing_type: inserted_sara_local_user.default_listing_type,
interface_language: inserted_sara_local_user.interface_language,
show_avatars: inserted_sara_local_user.show_avatars,
send_notifications_to_email: inserted_sara_local_user.send_notifications_to_email,
show_bot_accounts: inserted_sara_local_user.show_bot_accounts,
show_read_posts: inserted_sara_local_user.show_read_posts,
email_verified: inserted_sara_local_user.email_verified,
accepted_application: inserted_sara_local_user.accepted_application,
totp_2fa_secret: inserted_sara_local_user.totp_2fa_secret,
password_encrypted: inserted_sara_local_user.password_encrypted,
open_links_in_new_tab: inserted_sara_local_user.open_links_in_new_tab,
infinite_scroll_enabled: inserted_sara_local_user.infinite_scroll_enabled,
post_listing_mode: inserted_sara_local_user.post_listing_mode,
totp_2fa_enabled: inserted_sara_local_user.totp_2fa_enabled,
enable_keyboard_navigation: inserted_sara_local_user.enable_keyboard_navigation,
enable_animated_images: inserted_sara_local_user.enable_animated_images,
enable_private_messages: inserted_sara_local_user.enable_private_messages,
collapse_bot_comments: inserted_sara_local_user.collapse_bot_comments,
last_donation_notification: inserted_sara_local_user.last_donation_notification,
id: sara_local_user.id,
person_id: sara_local_user.person_id,
email: sara_local_user.email,
show_nsfw: sara_local_user.show_nsfw,
blur_nsfw: sara_local_user.blur_nsfw,
theme: sara_local_user.theme,
default_post_sort_type: sara_local_user.default_post_sort_type,
default_comment_sort_type: sara_local_user.default_comment_sort_type,
default_listing_type: sara_local_user.default_listing_type,
interface_language: sara_local_user.interface_language,
show_avatars: sara_local_user.show_avatars,
send_notifications_to_email: sara_local_user.send_notifications_to_email,
show_bot_accounts: sara_local_user.show_bot_accounts,
show_read_posts: sara_local_user.show_read_posts,
email_verified: sara_local_user.email_verified,
accepted_application: sara_local_user.accepted_application,
totp_2fa_secret: sara_local_user.totp_2fa_secret,
password_encrypted: sara_local_user.password_encrypted,
open_links_in_new_tab: sara_local_user.open_links_in_new_tab,
infinite_scroll_enabled: sara_local_user.infinite_scroll_enabled,
post_listing_mode: sara_local_user.post_listing_mode,
totp_2fa_enabled: sara_local_user.totp_2fa_enabled,
enable_keyboard_navigation: sara_local_user.enable_keyboard_navigation,
enable_animated_images: sara_local_user.enable_animated_images,
enable_private_messages: sara_local_user.enable_private_messages,
collapse_bot_comments: sara_local_user.collapse_bot_comments,
last_donation_notification: sara_local_user.last_donation_notification,
show_upvotes: sara_local_user.show_upvotes,
show_downvotes: sara_local_user.show_downvotes,
..Default::default()
},
creator: Person {
id: inserted_sara_person.id,
name: inserted_sara_person.name.clone(),
id: sara_person.id,
name: sara_person.name.clone(),
display_name: None,
published: inserted_sara_person.published,
published: sara_person.published,
avatar: None,
ap_id: inserted_sara_person.ap_id.clone(),
ap_id: sara_person.ap_id.clone(),
local: true,
banned: false,
ban_expires: None,
@ -234,12 +236,16 @@ mod tests {
bio: None,
banner: None,
updated: None,
inbox_url: inserted_sara_person.inbox_url.clone(),
inbox_url: sara_person.inbox_url.clone(),
matrix_user_id: None,
instance_id: inserted_instance.id,
private_key: inserted_sara_person.private_key,
public_key: inserted_sara_person.public_key,
last_refreshed_at: inserted_sara_person.last_refreshed_at,
instance_id: instance.id,
private_key: sara_person.private_key,
public_key: sara_person.public_key,
last_refreshed_at: sara_person.last_refreshed_at,
post_count: 0,
post_score: 0,
comment_count: 0,
comment_score: 0,
},
admin: None,
};
@ -265,7 +271,7 @@ mod tests {
// Approve the application
let approve_form = RegistrationApplicationUpdateForm {
admin_id: Some(Some(inserted_timmy_person.id)),
admin_id: Some(Some(timmy_person.id)),
deny_reason: None,
};
@ -277,7 +283,7 @@ mod tests {
..Default::default()
};
LocalUser::update(pool, inserted_sara_local_user.id, &approve_local_user_form).await?;
LocalUser::update(pool, sara_local_user.id, &approve_local_user_form).await?;
let read_sara_app_view_after_approve =
RegistrationApplicationView::read(pool, sara_app.id).await?;
@ -286,15 +292,15 @@ mod tests {
expected_sara_app_view
.creator_local_user
.accepted_application = true;
expected_sara_app_view.registration_application.admin_id = Some(inserted_timmy_person.id);
expected_sara_app_view.registration_application.admin_id = Some(timmy_person.id);
expected_sara_app_view.admin = Some(Person {
id: inserted_timmy_person.id,
name: inserted_timmy_person.name.clone(),
id: timmy_person.id,
name: timmy_person.name.clone(),
display_name: None,
published: inserted_timmy_person.published,
published: timmy_person.published,
avatar: None,
ap_id: inserted_timmy_person.ap_id.clone(),
ap_id: timmy_person.ap_id.clone(),
local: true,
banned: false,
ban_expires: None,
@ -303,12 +309,16 @@ mod tests {
bio: None,
banner: None,
updated: None,
inbox_url: inserted_timmy_person.inbox_url.clone(),
inbox_url: timmy_person.inbox_url.clone(),
matrix_user_id: None,
instance_id: inserted_instance.id,
private_key: inserted_timmy_person.private_key,
public_key: inserted_timmy_person.public_key,
last_refreshed_at: inserted_timmy_person.last_refreshed_at,
instance_id: instance.id,
private_key: timmy_person.private_key,
public_key: timmy_person.public_key,
last_refreshed_at: timmy_person.last_refreshed_at,
post_count: 0,
post_score: 0,
comment_count: 0,
comment_score: 0,
});
assert_eq!(read_sara_app_view_after_approve, expected_sara_app_view);
@ -331,10 +341,10 @@ mod tests {
let all_apps = RegistrationApplicationQuery::default().list(pool).await?;
assert_eq!(all_apps.len(), 2);
Person::delete(pool, inserted_timmy_person.id).await?;
Person::delete(pool, inserted_sara_person.id).await?;
Person::delete(pool, timmy_person.id).await?;
Person::delete(pool, sara_person.id).await?;
Person::delete(pool, inserted_jess_person.id).await?;
Instance::delete(pool, inserted_instance.id).await?;
Instance::delete(pool, instance.id).await?;
Ok(())
}

View file

@ -16,7 +16,6 @@ use lemmy_db_schema::{
schema::{
comment,
comment_actions,
comment_aggregates,
comment_report,
community,
community_actions,
@ -48,9 +47,6 @@ impl CommentReportView {
let comment_creator_join = aliases::person1.on(comment::creator_id.eq(recipient_id));
let comment_aggregates_join =
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id));
let comment_actions_join = comment_actions::table.on(
comment_actions::comment_id
.eq(comment_report::comment_id)
@ -88,7 +84,6 @@ impl CommentReportView {
.inner_join(community_join)
.inner_join(report_creator_join)
.inner_join(comment_creator_join)
.inner_join(comment_aggregates_join)
.left_join(comment_actions_join)
.left_join(resolver_join)
.left_join(creator_community_actions_join)
@ -115,7 +110,6 @@ impl CommentReportView {
community::all_columns,
person::all_columns,
aliases::person1.fields(person::all_columns),
comment_aggregates::all_columns,
coalesce(
creator_community_actions
.field(community_actions::received_ban)

View file

@ -20,7 +20,6 @@ use lemmy_db_schema::{
person_actions,
post,
post_actions,
post_aggregates,
post_report,
},
utils::{functions::coalesce, get_conn, DbPool},
@ -73,9 +72,6 @@ impl PostReportView {
.and(person_actions::person_id.eq(my_person_id)),
);
let post_aggregates_join =
post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id));
let resolver_join = aliases::person2.on(post_report::resolver_id.eq(resolver_id.nullable()));
post_report::table
@ -88,7 +84,6 @@ impl PostReportView {
.left_join(local_user_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
.inner_join(post_aggregates_join)
.left_join(resolver_join)
}
@ -126,10 +121,9 @@ impl PostReportView {
person_actions::blocked.nullable().is_not_null(),
post_actions::like_score.nullable(),
coalesce(
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments,
post::comments.nullable() - post_actions::read_comments_amount.nullable(),
post::comments,
),
post_aggregates::all_columns,
aliases::person2.fields(person::all_columns.nullable()),
))
.first(conn)

View file

@ -2,7 +2,7 @@ use crate::structs::SiteView;
use diesel::{ExpressionMethods, JoinOnDsl, OptionalExtension, QueryDsl, SelectableHelper};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
schema::{local_site, local_site_rate_limit, site, site_aggregates},
schema::{local_site, local_site_rate_limit, site},
utils::{get_conn, DbPool},
};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
@ -16,7 +16,6 @@ impl SiteView {
.inner_join(
local_site_rate_limit::table.on(local_site::id.eq(local_site_rate_limit::local_site_id)),
)
.inner_join(site_aggregates::table)
.select(Self::as_select())
.first(conn)
.await

View file

@ -164,7 +164,7 @@ mod tests {
let sara_post_vote_form = PostLikeForm::new(inserted_post.id, inserted_sara.id, -1);
PostLike::like(pool, &sara_post_vote_form).await?;
let expected_post_vote_views = [
let mut expected_post_vote_views = [
VoteView {
creator: inserted_sara.clone(),
creator_banned_from_community: false,
@ -176,6 +176,8 @@ mod tests {
score: 1,
},
];
expected_post_vote_views[1].creator.post_count = 1;
expected_post_vote_views[1].creator.comment_count = 1;
let read_post_vote_views = VoteView::list_for_post(pool, inserted_post.id, None, None).await?;
assert_eq!(read_post_vote_views, expected_post_vote_views);
@ -196,7 +198,7 @@ mod tests {
};
CommentLike::like(pool, &sara_comment_vote_form).await?;
let expected_comment_vote_views = [
let mut expected_comment_vote_views = [
VoteView {
creator: inserted_timmy.clone(),
creator_banned_from_community: false,
@ -208,6 +210,8 @@ mod tests {
score: 1,
},
];
expected_comment_vote_views[0].creator.post_count = 1;
expected_comment_vote_views[0].creator.comment_count = 1;
let read_comment_vote_views =
VoteView::list_for_comment(pool, inserted_comment.id, None, None).await?;

View file

@ -15,14 +15,17 @@ use diesel::{
Queryable,
Selectable,
};
#[cfg(feature = "full")]
use lemmy_db_schema::{
aliases::{creator_community_actions, creator_local_user, person1},
impls::comment::comment_select_remove_deletes,
impls::community::community_follower_select_subscribed_type,
impls::local_user::local_user_can_mod,
schema::{comment, comment_actions, community_actions, local_user, person, person_actions},
utils::functions::coalesce,
Person1AliasAllColumnsTuple,
};
use lemmy_db_schema::{
aggregates::structs::{
CommentAggregates,
CommunityAggregates,
PersonAggregates,
PostAggregates,
SiteAggregates,
},
source::{
comment::Comment,
comment_reply::CommentReply,
@ -36,7 +39,6 @@ use lemmy_db_schema::{
local_site::LocalSite,
local_site_rate_limit::LocalSiteRateLimit,
local_user::LocalUser,
local_user_vote_display_mode::LocalUserVoteDisplayMode,
mod_log::{
admin::{
AdminAllowInstance,
@ -73,16 +75,6 @@ use lemmy_db_schema::{
},
SubscribedType,
};
#[cfg(feature = "full")]
use lemmy_db_schema::{
aliases::{creator_community_actions, creator_local_user, person1},
impls::comment::comment_select_remove_deletes,
impls::community::community_follower_select_subscribed_type,
impls::local_user::local_user_can_mod,
schema::{comment, comment_actions, community_actions, local_user, person, person_actions},
utils::functions::coalesce,
Person1AliasAllColumnsTuple,
};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
@ -101,7 +93,6 @@ pub struct CommentReportView {
pub community: Community,
pub creator: Person,
pub comment_creator: Person,
pub counts: CommentAggregates,
pub creator_banned_from_community: bool,
pub creator_is_moderator: bool,
pub creator_is_admin: bool,
@ -135,8 +126,6 @@ pub struct CommentView {
pub post: Post,
#[cfg_attr(feature = "full", diesel(embed))]
pub community: Community,
#[cfg_attr(feature = "full", diesel(embed))]
pub counts: CommentAggregates,
#[cfg_attr(feature = "full",
diesel(
select_expression =
@ -222,7 +211,6 @@ pub struct CommentView {
pub struct CommentSlimView {
pub comment: Comment,
pub creator: Person,
pub counts: CommentAggregates,
pub creator_banned_from_community: bool,
pub banned_from_community: bool,
pub creator_is_moderator: bool,
@ -247,7 +235,6 @@ pub struct CommunityReportView {
pub community_report: CommunityReport,
pub community: Community,
pub creator: Person,
pub counts: CommunityAggregates,
pub subscribed: SubscribedType,
#[cfg_attr(feature = "full", ts(optional))]
pub resolver: Option<Person>,
@ -262,11 +249,7 @@ pub struct LocalUserView {
#[cfg_attr(feature = "full", diesel(embed))]
pub local_user: LocalUser,
#[cfg_attr(feature = "full", diesel(embed))]
pub local_user_vote_display_mode: LocalUserVoteDisplayMode,
#[cfg_attr(feature = "full", diesel(embed))]
pub person: Person,
#[cfg_attr(feature = "full", diesel(embed))]
pub counts: PersonAggregates,
}
#[skip_serializing_none]
@ -294,7 +277,6 @@ pub struct PostReportView {
#[cfg_attr(feature = "full", ts(optional))]
pub my_vote: Option<i16>,
pub unread_comments: i64,
pub counts: PostAggregates,
#[cfg_attr(feature = "full", ts(optional))]
pub resolver: Option<Person>,
}
@ -325,7 +307,6 @@ pub struct PostView {
pub banned_from_community: bool,
pub creator_is_moderator: bool,
pub creator_is_admin: bool,
pub counts: PostAggregates,
pub subscribed: SubscribedType,
#[cfg_attr(feature = "full", ts(optional))]
/// The time when the post was saved.
@ -390,8 +371,6 @@ pub struct SiteView {
pub local_site: LocalSite,
#[cfg_attr(feature = "full", diesel(embed))]
pub local_site_rate_limit: LocalSiteRateLimit,
#[cfg_attr(feature = "full", diesel(embed))]
pub counts: SiteAggregates,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -437,7 +416,6 @@ pub struct ReportCombinedViewInternal {
// Post-specific
pub post_report: Option<PostReport>,
pub post: Option<Post>,
pub post_counts: Option<PostAggregates>,
pub post_unread_comments: Option<i64>,
pub post_saved: Option<DateTime<Utc>>,
pub post_read: bool,
@ -446,7 +424,6 @@ pub struct ReportCombinedViewInternal {
// Comment-specific
pub comment_report: Option<CommentReport>,
pub comment: Option<Comment>,
pub comment_counts: Option<CommentAggregates>,
pub comment_saved: Option<DateTime<Utc>>,
pub my_comment_vote: Option<i16>,
// Private-message-specific
@ -454,7 +431,6 @@ pub struct ReportCombinedViewInternal {
pub private_message: Option<PrivateMessage>,
// Community-specific
pub community_report: Option<CommunityReport>,
pub community_counts: Option<CommunityAggregates>,
// Shared
pub report_creator: Person,
pub item_creator: Option<Person>,
@ -485,7 +461,6 @@ pub enum ReportCombinedView {
/// A combined person_content view
pub(crate) struct PersonContentCombinedViewInternal {
// Post-specific
pub post_counts: PostAggregates,
pub post_unread_comments: i64,
pub post_saved: Option<DateTime<Utc>>,
pub post_read: bool,
@ -495,7 +470,6 @@ pub(crate) struct PersonContentCombinedViewInternal {
pub post_tags: PostTags,
// Comment-specific
pub comment: Option<Comment>,
pub comment_counts: Option<CommentAggregates>,
pub comment_saved: Option<DateTime<Utc>>,
pub my_comment_vote: Option<i16>,
// Shared
@ -527,7 +501,6 @@ pub enum PersonContentCombinedView {
/// A combined person_saved view
pub(crate) struct PersonSavedCombinedViewInternal {
// Post-specific
pub post_counts: PostAggregates,
pub post_unread_comments: i64,
pub post_saved: Option<DateTime<Utc>>,
pub post_read: bool,
@ -537,7 +510,6 @@ pub(crate) struct PersonSavedCombinedViewInternal {
pub post_tags: PostTags,
// Comment-specific
pub comment: Option<Comment>,
pub comment_counts: Option<CommentAggregates>,
pub comment_saved: Option<DateTime<Utc>>,
pub my_comment_vote: Option<i16>,
// Shared
@ -618,8 +590,6 @@ pub struct CommunityView {
)
)]
pub blocked: bool,
#[cfg_attr(feature = "full", diesel(embed))]
pub counts: CommunityAggregates,
#[cfg_attr(feature = "full",
diesel(
select_expression = community_actions::received_ban.nullable().is_not_null()
@ -670,7 +640,6 @@ pub struct PersonCommentMentionView {
pub post: Post,
pub community: Community,
pub recipient: Person,
pub counts: CommentAggregates,
pub creator_banned_from_community: bool,
pub banned_from_community: bool,
pub creator_is_moderator: bool,
@ -699,7 +668,6 @@ pub struct PersonPostMentionView {
#[cfg_attr(feature = "full", ts(optional))]
pub image_details: Option<ImageDetails>,
pub recipient: Person,
pub counts: PostAggregates,
pub creator_banned_from_community: bool,
pub banned_from_community: bool,
pub creator_is_moderator: bool,
@ -731,7 +699,6 @@ pub struct CommentReplyView {
pub post: Post,
pub community: Community,
pub recipient: Person,
pub counts: CommentAggregates,
pub creator_banned_from_community: bool,
pub banned_from_community: bool,
pub creator_is_moderator: bool,
@ -754,8 +721,6 @@ pub struct CommentReplyView {
pub struct PersonView {
#[cfg_attr(feature = "full", diesel(embed))]
pub person: Person,
#[cfg_attr(feature = "full", diesel(embed))]
pub counts: PersonAggregates,
#[cfg_attr(feature = "full",
diesel(
select_expression_type = coalesce<diesel::sql_types::Bool, Nullable<local_user::admin>, bool>,
@ -806,7 +771,6 @@ pub struct InboxCombinedViewInternal {
pub person_comment_mention: Option<PersonCommentMention>,
// Person post mention
pub person_post_mention: Option<PersonPostMention>,
pub post_counts: Option<PostAggregates>,
pub post_unread_comments: Option<i64>,
pub post_saved: Option<DateTime<Utc>>,
pub post_read: bool,
@ -820,7 +784,6 @@ pub struct InboxCombinedViewInternal {
pub post: Option<Post>,
pub community: Option<Community>,
pub comment: Option<Comment>,
pub comment_counts: Option<CommentAggregates>,
pub comment_saved: Option<DateTime<Utc>>,
pub my_comment_vote: Option<i16>,
pub subscribed: SubscribedType,
@ -1170,7 +1133,6 @@ pub enum ModlogCombinedView {
pub(crate) struct SearchCombinedViewInternal {
// Post-specific
pub post: Option<Post>,
pub post_counts: Option<PostAggregates>,
pub post_unread_comments: Option<i64>,
pub post_saved: Option<DateTime<Utc>>,
pub post_read: bool,
@ -1180,16 +1142,12 @@ pub(crate) struct SearchCombinedViewInternal {
pub post_tags: PostTags,
// // Comment-specific
pub comment: Option<Comment>,
pub comment_counts: Option<CommentAggregates>,
pub comment_saved: Option<DateTime<Utc>>,
pub my_comment_vote: Option<i16>,
// // Community-specific
pub community: Option<Community>,
pub community_counts: Option<CommunityAggregates>,
pub community_blocked: bool,
pub subscribed: SubscribedType,
// Person
pub item_creator_counts: Option<PersonAggregates>,
// Shared
pub item_creator: Option<Person>,
pub item_creator_is_admin: bool,

View file

@ -482,9 +482,9 @@ fn create_post_items(posts: Vec<PostView>, settings: &Settings) -> LemmyResult<V
&p.creator.name,
community_url,
&p.community.name,
p.counts.score,
p.post.score,
post_url,
p.counts.comments);
p.post.comments);
// If its a url post, add it to the description
// and see if we can parse it as a media enclosure.

View file

@ -59,12 +59,12 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
protocols: Some(vec!["activitypub".to_string()]),
usage: Some(NodeInfoUsage {
users: Some(NodeInfoUsers {
total: Some(site_view.counts.users),
active_halfyear: Some(site_view.counts.users_active_half_year),
active_month: Some(site_view.counts.users_active_month),
total: Some(site_view.local_site.users),
active_halfyear: Some(site_view.local_site.users_active_half_year),
active_month: Some(site_view.local_site.users_active_month),
}),
local_posts: Some(site_view.counts.posts),
local_comments: Some(site_view.counts.comments),
local_posts: Some(site_view.local_site.posts),
local_comments: Some(site_view.local_site.comments),
}),
open_registrations,
services: Some(NodeInfoServices {

View file

@ -207,17 +207,15 @@ async fn process_ranks_in_batches(
// Raw `sql_query` is used as a performance optimization - Diesel does not support doing this
// in a single query (neither as a CTE, nor using a subquery)
let updated_rows = sql_query(format!(
r#"WITH batch AS (SELECT a.{id_column}
FROM {aggregates_table} a
r#"WITH batch AS (SELECT a.id
FROM {table_name} a
WHERE a.published > $1 AND ({where_clause})
ORDER BY a.published
LIMIT $2
FOR UPDATE SKIP LOCKED)
UPDATE {aggregates_table} a {set_clause}
FROM batch WHERE a.{id_column} = batch.{id_column} RETURNING a.published;
UPDATE {table_name} a {set_clause}
FROM batch WHERE a.id = batch.id RETURNING a.published;
"#,
id_column = format_args!("{table_name}_id"),
aggregates_table = format_args!("{table_name}_aggregates"),
))
.bind::<Timestamptz, _>(previous_batch_last_published)
.bind::<Integer, _>(update_batch_size)
@ -247,20 +245,20 @@ async fn process_post_aggregates_ranks_in_batches(conn: &mut AsyncPgConnection)
let mut previous_batch_result = Some(process_start_time);
while let Some(previous_batch_last_published) = previous_batch_result {
let updated_rows = sql_query(
r#"WITH batch AS (SELECT pa.post_id
FROM post_aggregates pa
r#"WITH batch AS (SELECT pa.id
FROM post pa
WHERE pa.published > $1
AND (pa.hot_rank != 0 OR pa.hot_rank_active != 0)
ORDER BY pa.published
LIMIT $2
FOR UPDATE SKIP LOCKED)
UPDATE post_aggregates pa
UPDATE post pa
SET hot_rank = r.hot_rank(pa.score, pa.published),
hot_rank_active = r.hot_rank(pa.score, pa.newest_comment_time_necro),
scaled_rank = r.scaled_rank(pa.score, pa.published, ca.interactions_month)
FROM batch, community_aggregates ca
WHERE pa.post_id = batch.post_id
AND pa.community_id = ca.community_id
FROM batch, community ca
WHERE pa.id = batch.id
AND pa.community_id = ca.id
RETURNING pa.published;
"#,
)
@ -368,16 +366,16 @@ async fn active_counts(pool: &mut DbPool<'_>) -> LemmyResult<()> {
for (full_form, abbr) in &intervals {
let update_site_stmt = format!(
"update site_aggregates set users_active_{} = (select r.site_aggregates_activity('{}')) where site_id = 1",
"update local_site set users_active_{} = (select r.site_aggregates_activity('{}')) where site_id = 1",
abbr, full_form
);
sql_query(update_site_stmt).execute(&mut conn).await?;
let update_community_stmt = format!("update community_aggregates ca set users_active_{} = mv.count_ from r.community_aggregates_activity('{}') mv where ca.community_id = mv.community_id_", abbr, full_form);
let update_community_stmt = format!("update community ca set users_active_{} = mv.count_ from r.community_aggregates_activity('{}') mv where ca.id = mv.community_id_", abbr, full_form);
sql_query(update_community_stmt).execute(&mut conn).await?;
}
let update_interactions_stmt = "update community_aggregates ca set interactions_month = mv.count_ from r.community_aggregates_interactions('1 month') mv where ca.community_id = mv.community_id_";
let update_interactions_stmt = "update community ca set interactions_month = mv.count_ from r.community_aggregates_interactions('1 month') mv where ca.id = mv.community_id_";
sql_query(update_interactions_stmt)
.execute(&mut conn)
.await?;

View file

@ -30,6 +30,9 @@ pub const CACHE_DURATION_FEDERATION: Duration = Duration::from_millis(500);
#[cfg(not(debug_assertions))]
pub const CACHE_DURATION_FEDERATION: Duration = Duration::from_secs(60);
#[cfg(debug_assertions)]
pub const CACHE_DURATION_API: Duration = Duration::from_secs(0);
#[cfg(not(debug_assertions))]
pub const CACHE_DURATION_API: Duration = Duration::from_secs(1);
pub const MAX_COMMENT_DEPTH_LIMIT: usize = 50;

View file

@ -0,0 +1,351 @@
-- move comment_aggregates back into separate table
CREATE TABLE comment_aggregates (
comment_id int PRIMARY KEY NOT NULL REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
score bigint NOT NULL DEFAULT 0,
upvotes bigint NOT NULL DEFAULT 0,
downvotes bigint NOT NULL DEFAULT 0,
published timestamp with time zone NOT NULL DEFAULT now(),
child_count integer NOT NULL DEFAULT 0,
hot_rank double precision NOT NULL DEFAULT 0.0001,
controversy_rank double precision NOT NULL DEFAULT 0,
report_count smallint NOT NULL DEFAULT 0,
unresolved_report_count smallint NOT NULL DEFAULT 0
);
INSERT INTO comment_aggregates
SELECT
id AS comment_id,
score,
upvotes,
downvotes,
published,
child_count,
hot_rank,
controversy_rank,
report_count,
unresolved_report_count
FROM
comment;
ALTER TABLE comment
DROP COLUMN score,
DROP COLUMN upvotes,
DROP COLUMN downvotes,
DROP COLUMN child_count,
DROP COLUMN hot_rank,
DROP COLUMN controversy_rank,
DROP COLUMN report_count,
DROP COLUMN unresolved_report_count;
CREATE INDEX idx_comment_aggregates_controversy ON comment_aggregates USING btree (controversy_rank DESC);
CREATE INDEX idx_comment_aggregates_hot ON comment_aggregates USING btree (hot_rank DESC, score DESC);
CREATE INDEX idx_comment_aggregates_nonzero_hotrank ON comment_aggregates USING btree (published)
WHERE (hot_rank <> (0)::double precision);
CREATE INDEX idx_comment_aggregates_published ON comment_aggregates USING btree (published DESC);
CREATE INDEX idx_comment_aggregates_score ON comment_aggregates USING btree (score DESC);
-- move comment_aggregates back into separate table
CREATE TABLE post_aggregates (
post_id int PRIMARY KEY NOT NULL REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
comments bigint NOT NULL DEFAULT 0,
score bigint NOT NULL DEFAULT 0,
upvotes bigint NOT NULL DEFAULT 0,
downvotes bigint NOT NULL DEFAULT 0,
published timestamp with time zone NOT NULL DEFAULT now(),
newest_comment_time_necro timestamp with time zone NOT NULL DEFAULT now(),
newest_comment_time timestamp with time zone NOT NULL DEFAULT now(),
featured_community boolean NOT NULL DEFAULT FALSE,
featured_local boolean NOT NULL DEFAULT FALSE,
hot_rank double precision NOT NULL DEFAULT 0.0001,
hot_rank_active double precision NOT NULL DEFAULT 0.0001,
community_id integer NOT NULL REFERENCES community (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
creator_id integer NOT NULL REFERENCES person (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
controversy_rank double precision NOT NULL DEFAULT 0,
instance_id integer NOT NULL REFERENCES instance (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
scaled_rank double precision NOT NULL DEFAULT 0.0001,
report_count smallint NOT NULL DEFAULT 0,
unresolved_report_count smallint NOT NULL DEFAULT 0
);
INSERT INTO post_aggregates
SELECT
id AS post_id,
comments,
score,
upvotes,
downvotes,
published,
newest_comment_time_necro,
newest_comment_time,
featured_community,
featured_local,
hot_rank,
hot_rank_active,
community_id,
creator_id,
controversy_rank,
instance_id,
scaled_rank,
report_count,
unresolved_report_count
FROM
post;
ALTER TABLE post
DROP COLUMN comments,
DROP COLUMN score,
DROP COLUMN upvotes,
DROP COLUMN downvotes,
DROP COLUMN newest_comment_time_necro,
DROP COLUMN newest_comment_time,
DROP COLUMN hot_rank,
DROP COLUMN hot_rank_active,
DROP COLUMN controversy_rank,
DROP COLUMN instance_id,
DROP COLUMN scaled_rank,
DROP COLUMN report_count,
DROP COLUMN unresolved_report_count;
CREATE INDEX idx_post_aggregates_community_active ON post_aggregates USING btree (community_id, featured_local DESC, hot_rank_active DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_community_controversy ON post_aggregates USING btree (community_id, featured_local DESC, controversy_rank DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_community_hot ON post_aggregates USING btree (community_id, featured_local DESC, hot_rank DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_community_most_comments ON post_aggregates USING btree (community_id, featured_local DESC, comments DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_community_newest_comment_time ON post_aggregates USING btree (community_id, featured_local DESC, newest_comment_time DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_community_newest_comment_time_necro ON post_aggregates USING btree (community_id, featured_local DESC, newest_comment_time_necro DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_community_published ON post_aggregates USING btree (community_id, featured_local DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_community_published_asc ON post_aggregates USING btree (community_id, featured_local DESC, reverse_timestamp_sort (published) DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_community_scaled ON post_aggregates USING btree (community_id, featured_local DESC, scaled_rank DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_community_score ON post_aggregates USING btree (community_id, featured_local DESC, score DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_active ON post_aggregates USING btree (community_id, featured_community DESC, hot_rank_active DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_controversy ON post_aggregates USING btree (community_id, featured_community DESC, controversy_rank DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_hot ON post_aggregates USING btree (community_id, featured_community DESC, hot_rank DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_most_comments ON post_aggregates USING btree (community_id, featured_community DESC, comments DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_newest_comment_time ON post_aggregates USING btree (community_id, featured_community DESC, newest_comment_time DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_newest_comment_time_necr ON post_aggregates USING btree (community_id, featured_community DESC, newest_comment_time_necro DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_published ON post_aggregates USING btree (community_id, featured_community DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_published_asc ON post_aggregates USING btree (community_id, featured_community DESC, reverse_timestamp_sort (published) DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_scaled ON post_aggregates USING btree (community_id, featured_community DESC, scaled_rank DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_community_score ON post_aggregates USING btree (community_id, featured_community DESC, score DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_active ON post_aggregates USING btree (featured_local DESC, hot_rank_active DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_controversy ON post_aggregates USING btree (featured_local DESC, controversy_rank DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_hot ON post_aggregates USING btree (featured_local DESC, hot_rank DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_most_comments ON post_aggregates USING btree (featured_local DESC, comments DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_newest_comment_time ON post_aggregates USING btree (featured_local DESC, newest_comment_time DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_newest_comment_time_necro ON post_aggregates USING btree (featured_local DESC, newest_comment_time_necro DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_published ON post_aggregates USING btree (featured_local DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_published_asc ON post_aggregates USING btree (featured_local DESC, reverse_timestamp_sort (published) DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_scaled ON post_aggregates USING btree (featured_local DESC, scaled_rank DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_featured_local_score ON post_aggregates USING btree (featured_local DESC, score DESC, published DESC, post_id DESC);
CREATE INDEX idx_post_aggregates_nonzero_hotrank ON post_aggregates USING btree (published DESC)
WHERE ((hot_rank <> (0)::double precision) OR (hot_rank_active <> (0)::double precision));
CREATE INDEX idx_post_aggregates_published ON post_aggregates USING btree (published DESC);
CREATE INDEX idx_post_aggregates_published_asc ON post_aggregates USING btree (reverse_timestamp_sort (published) DESC);
DROP INDEX idx_post_featured_community_published_asc;
DROP INDEX idx_post_featured_local_published;
DROP INDEX idx_post_featured_local_published_asc;
DROP INDEX idx_post_published;
DROP INDEX idx_post_published_asc;
DROP INDEX idx_search_combined_score;
-- move community_aggregates back into separate table
CREATE TABLE community_aggregates (
community_id int PRIMARY KEY NOT NULL REFERENCES COMMunity ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
subscribers bigint NOT NULL DEFAULT 0,
posts bigint NOT NULL DEFAULT 0,
comments bigint NOT NULL DEFAULT 0,
published timestamp with time zone DEFAULT now() NOT NULL,
users_active_day bigint NOT NULL DEFAULT 0,
users_active_week bigint NOT NULL DEFAULT 0,
users_active_month bigint NOT NULL DEFAULT 0,
users_active_half_year bigint NOT NULL DEFAULT 0,
hot_rank double precision NOT NULL DEFAULT 0.0001,
subscribers_local bigint NOT NULL DEFAULT 0,
report_count smallint NOT NULL DEFAULT 0,
unresolved_report_count smallint NOT NULL DEFAULT 0,
interactions_month bigint NOT NULL DEFAULT 0
);
INSERT INTO community_aggregates
SELECT
id AS comment_id,
subscribers,
posts,
comments,
published,
users_active_day,
users_active_week,
users_active_month,
users_active_half_year,
hot_rank,
subscribers_local,
report_count,
unresolved_report_count,
interactions_month
FROM
community;
ALTER TABLE community
DROP COLUMN subscribers,
DROP COLUMN posts,
DROP COLUMN comments,
DROP COLUMN users_active_day,
DROP COLUMN users_active_week,
DROP COLUMN users_active_month,
DROP COLUMN users_active_half_year,
DROP COLUMN hot_rank,
DROP COLUMN subscribers_local,
DROP COLUMN report_count,
DROP COLUMN unresolved_report_count,
DROP COLUMN interactions_month,
ALTER CONSTRAINT community_instance_id_fkey NOT DEFERRABLE INITIALLY IMMEDIATE;
CREATE INDEX idx_community_aggregates_hot ON public.community_aggregates USING btree (hot_rank DESC);
CREATE INDEX idx_community_aggregates_nonzero_hotrank ON public.community_aggregates USING btree (published)
WHERE (hot_rank <> (0)::double precision);
CREATE INDEX idx_community_aggregates_published ON public.community_aggregates USING btree (published DESC);
CREATE INDEX idx_community_aggregates_subscribers ON public.community_aggregates USING btree (subscribers DESC);
CREATE INDEX idx_community_aggregates_users_active_month ON public.community_aggregates USING btree (users_active_month DESC);
-- move person_aggregates back into separate table
CREATE TABLE person_aggregates (
person_id int PRIMARY KEY NOT NULL REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
post_count bigint NOT NULL DEFAULT 0,
post_score bigint NOT NULL DEFAULT 0,
comment_count bigint NOT NULL DEFAULT 0,
comment_score bigint NOT NULL DEFAULT 0,
published timestamp with time zone DEFAULT now() NOT NULL
);
INSERT INTO person_aggregates
SELECT
id AS person_id,
post_count,
post_score,
comment_count,
comment_score,
published
FROM
person;
ALTER TABLE person
DROP COLUMN post_count,
DROP COLUMN post_score,
DROP COLUMN comment_count,
DROP COLUMN comment_score;
CREATE INDEX idx_person_aggregates_comment_score ON public.person_aggregates USING btree (comment_score DESC);
CREATE INDEX idx_person_aggregates_person ON public.person_aggregates USING btree (person_id);
-- move site_aggregates back into separate table
CREATE TABLE site_aggregates (
site_id int PRIMARY KEY NOT NULL REFERENCES site ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
users bigint NOT NULL DEFAULT 1,
posts bigint NOT NULL DEFAULT 0,
comments bigint NOT NULL DEFAULT 0,
communities bigint NOT NULL DEFAULT 0,
users_active_day bigint NOT NULL DEFAULT 0,
users_active_week bigint NOT NULL DEFAULT 0,
users_active_month bigint NOT NULL DEFAULT 0,
users_active_half_year bigint NOT NULL DEFAULT 0
);
INSERT INTO site_aggregates
SELECT
id AS site_id,
users,
posts,
comments,
communities,
users_active_day,
users_active_week,
users_active_month,
users_active_half_year
FROM
local_site;
ALTER TABLE local_site
DROP COLUMN users,
DROP COLUMN posts,
DROP COLUMN comments,
DROP COLUMN communities,
DROP COLUMN users_active_day,
DROP COLUMN users_active_week,
DROP COLUMN users_active_month,
DROP COLUMN users_active_half_year;
-- move local_user_vote_display_mode back into separate table
CREATE TABLE local_user_vote_display_mode (
local_user_id int PRIMARY KEY NOT NULL REFERENCES local_user ON UPDATE CASCADE ON DELETE CASCADE,
score boolean NOT NULL DEFAULT FALSE,
upvotes boolean NOT NULL DEFAULT TRUE,
downvotes boolean NOT NULL DEFAULT TRUE,
upvote_percentage boolean NOT NULL DEFAULT FALSE
);
INSERT INTO local_user_vote_display_mode
SELECT
id AS local_user_id,
show_score AS score,
show_upvotes AS upvotes,
show_downvotes AS downvotes,
show_upvote_percentage AS upvote_percentage
FROM
local_user;
ALTER TABLE local_user
DROP COLUMN show_score,
DROP COLUMN show_upvotes,
DROP COLUMN show_downvotes,
DROP COLUMN show_upvote_percentage;
CREATE INDEX idx_search_combined_score ON public.search_combined USING btree (score DESC, id DESC);
CREATE UNIQUE INDEX idx_site_aggregates_1_row_only ON public.site_aggregates USING btree ((TRUE));

View file

@ -0,0 +1,261 @@
-- merge comment_aggregates into comment table
ALTER TABLE comment
ADD COLUMN score bigint NOT NULL DEFAULT 0,
ADD COLUMN upvotes bigint NOT NULL DEFAULT 0,
ADD COLUMN downvotes bigint NOT NULL DEFAULT 0,
ADD COLUMN child_count integer NOT NULL DEFAULT 0,
ADD COLUMN hot_rank double precision NOT NULL DEFAULT 0.0001,
ADD COLUMN controversy_rank double precision NOT NULL DEFAULT 0,
ADD COLUMN report_count smallint NOT NULL DEFAULT 0,
ADD COLUMN unresolved_report_count smallint NOT NULL DEFAULT 0;
UPDATE
comment
SET
score = ca.score,
upvotes = ca.upvotes,
downvotes = ca.downvotes,
child_count = ca.child_count,
hot_rank = ca.hot_rank,
controversy_rank = ca.controversy_rank,
report_count = ca.report_count,
unresolved_report_count = ca.unresolved_report_count
FROM
comment_aggregates AS ca
WHERE
comment.id = ca.comment_id;
DROP TABLE comment_aggregates;
CREATE INDEX idx_comment_controversy ON comment USING btree (controversy_rank DESC);
CREATE INDEX idx_comment_hot ON comment USING btree (hot_rank DESC, score DESC);
CREATE INDEX idx_comment_nonzero_hotrank ON comment USING btree (published)
WHERE (hot_rank <> (0)::double precision);
--CREATE INDEX idx_comment_published on comment USING btree (published DESC);
CREATE INDEX idx_comment_score ON comment USING btree (score DESC);
-- merge post_aggregates into post table
ALTER TABLE post
ADD COLUMN comments bigint NOT NULL DEFAULT 0,
ADD COLUMN score bigint NOT NULL DEFAULT 0,
ADD COLUMN upvotes bigint NOT NULL DEFAULT 0,
ADD COLUMN downvotes bigint NOT NULL DEFAULT 0,
ADD COLUMN newest_comment_time_necro timestamp with time zone NOT NULL DEFAULT now(),
ADD COLUMN newest_comment_time timestamp with time zone NOT NULL DEFAULT now(),
ADD COLUMN hot_rank double precision NOT NULL DEFAULT 0.0001,
ADD COLUMN hot_rank_active double precision NOT NULL DEFAULT 0.0001,
ADD COLUMN controversy_rank double precision NOT NULL DEFAULT 0,
ADD COLUMN instance_id int NOT NULL DEFAULT 0 REFERENCES instance (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
ADD COLUMN scaled_rank double precision NOT NULL DEFAULT 0.0001,
ADD COLUMN report_count smallint NOT NULL DEFAULT 0,
ADD COLUMN unresolved_report_count smallint NOT NULL DEFAULT 0;
UPDATE
post
SET
comments = pa.comments,
score = pa.score,
upvotes = pa.upvotes,
downvotes = pa.downvotes,
newest_comment_time_necro = pa.newest_comment_time_necro,
newest_comment_time = pa.newest_comment_time,
hot_rank = pa.hot_rank,
hot_rank_active = pa.hot_rank_active,
controversy_rank = pa.controversy_rank,
instance_id = pa.instance_id,
scaled_rank = pa.scaled_rank,
report_count = pa.report_count,
unresolved_report_count = pa.unresolved_report_count
FROM
post_aggregates AS pa
WHERE
post.id = pa.post_id;
DROP TABLE post_aggregates;
-- Note, removed `post_id DESC` from all these
CREATE INDEX idx_post_community_active ON post USING btree (community_id, featured_local DESC, hot_rank_active DESC, published DESC);
CREATE INDEX idx_post_community_controversy ON post USING btree (community_id, featured_local DESC, controversy_rank DESC);
CREATE INDEX idx_post_community_hot ON post USING btree (community_id, featured_local DESC, hot_rank DESC, published DESC);
CREATE INDEX idx_post_community_most_comments ON post USING btree (community_id, featured_local DESC, comments DESC, published DESC);
CREATE INDEX idx_post_community_newest_comment_time ON post USING btree (community_id, featured_local DESC, newest_comment_time DESC);
CREATE INDEX idx_post_community_newest_comment_time_necro ON post USING btree (community_id, featured_local DESC, newest_comment_time_necro DESC);
-- INDEX idx_post_community_published ON post USING btree (community_id, featured_local DESC, published DESC);
--CREATE INDEX idx_post_community_published_asc ON post USING btree (community_id, featured_local DESC, reverse_timestamp_sort (published) DESC);
CREATE INDEX idx_post_community_scaled ON post USING btree (community_id, featured_local DESC, scaled_rank DESC, published DESC);
CREATE INDEX idx_post_community_score ON post USING btree (community_id, featured_local DESC, score DESC, published DESC);
CREATE INDEX idx_post_featured_community_active ON post USING btree (community_id, featured_community DESC, hot_rank_active DESC, published DESC);
CREATE INDEX idx_post_featured_community_controversy ON post USING btree (community_id, featured_community DESC, controversy_rank DESC);
CREATE INDEX idx_post_featured_community_hot ON post USING btree (community_id, featured_community DESC, hot_rank DESC, published DESC);
CREATE INDEX idx_post_featured_community_most_comments ON post USING btree (community_id, featured_community DESC, comments DESC, published DESC);
CREATE INDEX idx_post_featured_community_newest_comment_time ON post USING btree (community_id, featured_community DESC, newest_comment_time DESC);
CREATE INDEX idx_post_featured_community_newest_comment_time_necr ON post USING btree (community_id, featured_community DESC, newest_comment_time_necro DESC);
--CREATE INDEX idx_post_featured_community_published ON post USING btree (community_id, featured_community DESC, published DESC);
CREATE INDEX idx_post_featured_community_published_asc ON post USING btree (community_id, featured_community DESC, reverse_timestamp_sort (published) DESC);
CREATE INDEX idx_post_featured_community_scaled ON post USING btree (community_id, featured_community DESC, scaled_rank DESC, published DESC);
CREATE INDEX idx_post_featured_community_score ON post USING btree (community_id, featured_community DESC, score DESC, published DESC);
CREATE INDEX idx_post_featured_local_active ON post USING btree (featured_local DESC, hot_rank_active DESC, published DESC);
CREATE INDEX idx_post_featured_local_controversy ON post USING btree (featured_local DESC, controversy_rank DESC);
CREATE INDEX idx_post_featured_local_hot ON post USING btree (featured_local DESC, hot_rank DESC, published DESC);
CREATE INDEX idx_post_featured_local_most_comments ON post USING btree (featured_local DESC, comments DESC, published DESC);
CREATE INDEX idx_post_featured_local_newest_comment_time ON post USING btree (featured_local DESC, newest_comment_time DESC);
CREATE INDEX idx_post_featured_local_newest_comment_time_necro ON post USING btree (featured_local DESC, newest_comment_time_necro DESC);
CREATE INDEX idx_post_featured_local_published ON post USING btree (featured_local DESC, published DESC);
CREATE INDEX idx_post_featured_local_published_asc ON post USING btree (featured_local DESC, reverse_timestamp_sort (published) DESC);
CREATE INDEX idx_post_featured_local_scaled ON post USING btree (featured_local DESC, scaled_rank DESC, published DESC);
CREATE INDEX idx_post_featured_local_score ON post USING btree (featured_local DESC, score DESC, published DESC);
CREATE INDEX idx_post_nonzero_hotrank ON post USING btree (published DESC)
WHERE ((hot_rank <> (0)::double precision) OR (hot_rank_active <> (0)::double precision));
CREATE INDEX idx_post_published ON post USING btree (published DESC);
CREATE INDEX idx_post_published_asc ON post USING btree (reverse_timestamp_sort (published) DESC);
-- merge community_aggregates into community table
ALTER TABLE community
ADD COLUMN subscribers bigint NOT NULL DEFAULT 0,
ADD COLUMN posts bigint NOT NULL DEFAULT 0,
ADD COLUMN comments bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_day bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_week bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_month bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_half_year bigint NOT NULL DEFAULT 0,
ADD COLUMN hot_rank double precision NOT NULL DEFAULT 0.0001,
ADD COLUMN subscribers_local bigint NOT NULL DEFAULT 0,
ADD COLUMN report_count smallint NOT NULL DEFAULT 0,
ADD COLUMN unresolved_report_count smallint NOT NULL DEFAULT 0,
ADD COLUMN interactions_month bigint NOT NULL DEFAULT 0,
ALTER CONSTRAINT community_instance_id_fkey DEFERRABLE INITIALLY DEFERRED;
UPDATE
community
SET
subscribers = ca.subscribers,
posts = ca.posts,
comments = ca.comments,
users_active_day = ca.users_active_day,
users_active_week = ca.users_active_week,
users_active_month = ca.users_active_month,
users_active_half_year = ca.users_active_half_year,
hot_rank = ca.hot_rank,
subscribers_local = ca.subscribers_local,
report_count = ca.report_count,
unresolved_report_count = ca.unresolved_report_count,
interactions_month = ca.interactions_month
FROM
community_aggregates AS ca
WHERE
community.id = ca.community_id;
DROP TABLE community_aggregates;
CREATE INDEX idx_community_hot ON public.community USING btree (hot_rank DESC);
CREATE INDEX idx_community_nonzero_hotrank ON public.community USING btree (published)
WHERE (hot_rank <> (0)::double precision);
CREATE INDEX idx_community_subscribers ON public.community USING btree (subscribers DESC);
CREATE INDEX idx_community_users_active_month ON public.community USING btree (users_active_month DESC);
-- merge person_aggregates into person table
ALTER TABLE person
ADD COLUMN post_count bigint NOT NULL DEFAULT 0,
ADD COLUMN post_score bigint NOT NULL DEFAULT 0,
ADD COLUMN comment_count bigint NOT NULL DEFAULT 0,
ADD COLUMN comment_score bigint NOT NULL DEFAULT 0;
UPDATE
person
SET
post_count = pa.post_count,
post_score = pa.post_score,
comment_count = pa.comment_count,
comment_score = pa.comment_score
FROM
person_aggregates AS pa
WHERE
person.id = pa.person_id;
DROP TABLE person_aggregates;
-- merge site_aggregates into person table
ALTER TABLE local_site
ADD COLUMN users bigint NOT NULL DEFAULT 1,
ADD COLUMN posts bigint NOT NULL DEFAULT 0,
ADD COLUMN comments bigint NOT NULL DEFAULT 0,
ADD COLUMN communities bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_day bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_week bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_month bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_half_year bigint NOT NULL DEFAULT 0;
UPDATE
local_site
SET
users = sa.users,
posts = sa.posts,
comments = sa.comments,
communities = sa.communities,
users_active_day = sa.users_active_day,
users_active_week = sa.users_active_week,
users_active_month = sa.users_active_month,
users_active_half_year = sa.users_active_half_year
FROM
site_aggregates AS sa
WHERE
local_site.site_id = sa.site_id;
DROP TABLE site_aggregates;
-- merge local_user_vote_display_mode into local_user table
ALTER TABLE local_user
ADD COLUMN show_score boolean NOT NULL DEFAULT FALSE,
ADD COLUMN show_upvotes boolean NOT NULL DEFAULT TRUE,
ADD COLUMN show_downvotes boolean NOT NULL DEFAULT TRUE,
ADD COLUMN show_upvote_percentage boolean NOT NULL DEFAULT FALSE;
UPDATE
local_user
SET
show_score = v.score,
show_upvotes = v.upvotes,
show_downvotes = v.downvotes,
show_upvote_percentage = v.upvote_percentage
FROM
local_user_vote_display_mode AS v
WHERE
local_user.id = v.local_user_id;
DROP TABLE local_user_vote_display_mode;