pull/4459/merge
dullbananas 2024-07-02 16:07:00 -07:00 committed by GitHub
commit 992c643f55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 2053 additions and 1442 deletions

View File

@ -2,7 +2,7 @@
# See https://github.com/woodpecker-ci/woodpecker/issues/1677 # See https://github.com/woodpecker-ci/woodpecker/issues/1677
variables: variables:
- &rust_image "rust:1.78" - &rust_image "rust:1.79"
- &rust_nightly_image "rustlang/rust:nightly" - &rust_nightly_image "rustlang/rust:nightly"
- &install_pnpm "corepack enable pnpm" - &install_pnpm "corepack enable pnpm"
- &slow_check_paths - &slow_check_paths

17
Cargo.lock generated
View File

@ -1557,6 +1557,15 @@ dependencies = [
"tokio-postgres", "tokio-postgres",
] ]
[[package]]
name = "diesel-bind-if-some"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ed8ce9db476124d2eaf4c9db45dc6581b8e8c4c4d47d5e0f39de1fb55dfb2a7"
dependencies = [
"diesel",
]
[[package]] [[package]]
name = "diesel-derive-enum" name = "diesel-derive-enum"
version = "2.1.0" version = "2.1.0"
@ -3025,6 +3034,7 @@ dependencies = [
"derive-new", "derive-new",
"diesel", "diesel",
"diesel-async", "diesel-async",
"diesel-bind-if-some",
"diesel-derive-enum", "diesel-derive-enum",
"diesel-derive-newtype", "diesel-derive-newtype",
"diesel_ltree", "diesel_ltree",
@ -3048,6 +3058,7 @@ dependencies = [
"tokio-postgres-rustls", "tokio-postgres-rustls",
"tracing", "tracing",
"ts-rs", "ts-rs",
"tuplex",
"typed-builder", "typed-builder",
"url", "url",
"uuid", "uuid",
@ -6372,6 +6383,12 @@ dependencies = [
"termcolor", "termcolor",
] ]
[[package]]
name = "tuplex"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa"
[[package]] [[package]]
name = "typed-builder" name = "typed-builder"
version = "0.18.2" version = "0.18.2"

View File

@ -168,6 +168,8 @@ i-love-jesus = { version = "0.1.0" }
clap = { version = "4.5.6", features = ["derive", "env"] } clap = { version = "4.5.6", features = ["derive", "env"] }
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
derive-new = "0.6.0" derive-new = "0.6.0"
diesel-bind-if-some = "0.1.0"
tuplex = "0.1.2"
[dependencies] [dependencies]
lemmy_api = { workspace = true } lemmy_api = { workspace = true }

View File

@ -169,7 +169,6 @@ pub async fn update_read_comments(
person_id, person_id,
post_id, post_id,
read_comments, read_comments,
..PersonPostAggregatesForm::default()
}; };
PersonPostAggregates::upsert(pool, &person_post_agg_form) PersonPostAggregates::upsert(pool, &person_post_agg_form)

View File

@ -38,6 +38,8 @@ full = [
"tokio-postgres-rustls", "tokio-postgres-rustls",
"rustls", "rustls",
"i-love-jesus", "i-love-jesus",
"tuplex",
"diesel-bind-if-some",
] ]
[dependencies] [dependencies]
@ -80,8 +82,10 @@ rustls = { workspace = true, optional = true }
uuid = { workspace = true, features = ["v4"] } uuid = { workspace = true, features = ["v4"] }
i-love-jesus = { workspace = true, optional = true } i-love-jesus = { workspace = true, optional = true }
anyhow = { workspace = true } anyhow = { workspace = true }
diesel-bind-if-some = { workspace = true, optional = true }
moka.workspace = true moka.workspace = true
derive-new.workspace = true derive-new.workspace = true
tuplex = { workspace = true, optional = true }
[dev-dependencies] [dev-dependencies]
serial_test = { workspace = true } serial_test = { workspace = true }

View File

@ -38,7 +38,7 @@ AS $a$
BEGIN BEGIN
EXECUTE replace($b$ EXECUTE replace($b$
-- When a thing gets a vote, update its aggregates and its creator's aggregates -- When a thing gets a vote, update its aggregates and its creator's aggregates
CALL r.create_triggers ('thing_like', $$ CALL r.create_triggers ('thing_actions', $$
BEGIN BEGIN
WITH thing_diff AS ( UPDATE WITH thing_diff AS ( UPDATE
thing_aggregates AS a thing_aggregates AS a
@ -46,7 +46,8 @@ BEGIN
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) 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 ( FROM (
SELECT SELECT
(thing_like).thing_id, coalesce(sum(count_diff) FILTER (WHERE (thing_like).score = 1), 0) AS upvotes, coalesce(sum(count_diff) FILTER (WHERE (thing_like).score != 1), 0) AS downvotes FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (thing_like).thing_id) AS diff (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 WHERE
a.thing_id = diff.thing_id a.thing_id = diff.thing_id
AND (diff.upvotes, diff.downvotes) != (0, 0) AND (diff.upvotes, diff.downvotes) != (0, 0)
@ -360,7 +361,7 @@ CREATE TRIGGER comment_count
-- Count subscribers for communities. -- Count subscribers for communities.
-- subscribers should be updated only when a local community is followed by a local or remote person. -- 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. -- subscribers_local should be updated only when a local person follows a local or remote community.
CALL r.create_triggers ('community_follower', $$ CALL r.create_triggers ('community_actions', $$
BEGIN BEGIN
UPDATE UPDATE
community_aggregates AS a community_aggregates AS a
@ -368,10 +369,11 @@ BEGIN
subscribers = a.subscribers + diff.subscribers, subscribers_local = a.subscribers_local + diff.subscribers_local subscribers = a.subscribers + diff.subscribers, subscribers_local = a.subscribers_local + diff.subscribers_local
FROM ( FROM (
SELECT SELECT
(community_follower).community_id, coalesce(sum(count_diff) FILTER (WHERE community.local), 0) AS subscribers, coalesce(sum(count_diff) FILTER (WHERE person.local), 0) AS subscribers_local (community_actions).community_id, coalesce(sum(count_diff) FILTER (WHERE community.local), 0) AS subscribers, coalesce(sum(count_diff) FILTER (WHERE person.local), 0) AS subscribers_local
FROM select_old_and_new_rows AS old_and_new_rows FROM select_old_and_new_rows AS old_and_new_rows
LEFT JOIN community ON community.id = (community_follower).community_id LEFT JOIN community ON community.id = (community_actions).community_id
LEFT JOIN person ON person.id = (community_follower).person_id GROUP BY (community_follower).community_id) AS diff 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 WHERE
a.community_id = diff.community_id a.community_id = diff.community_id
AND (diff.subscribers, diff.subscribers_local) != (0, 0); AND (diff.subscribers, diff.subscribers_local) != (0, 0);
@ -541,7 +543,7 @@ CREATE FUNCTION r.delete_follow_before_person ()
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
BEGIN BEGIN
DELETE FROM community_follower AS c DELETE FROM community_actions AS c
WHERE c.person_id = OLD.id; WHERE c.person_id = OLD.id;
RETURN OLD; RETURN OLD;
END; END;

View File

@ -2,10 +2,17 @@ use crate::{
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm}, aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
diesel::OptionalExtension, diesel::OptionalExtension,
newtypes::{PersonId, PostId}, newtypes::{PersonId, PostId},
schema::person_post_aggregates::dsl::{person_id, person_post_aggregates, post_id}, schema::post_actions,
utils::{get_conn, DbPool}, utils::{find_action, get_conn, now, DbPool},
};
use diesel::{
expression::SelectableHelper,
insert_into,
result::Error,
ExpressionMethods,
NullableExpressionMethods,
QueryDsl,
}; };
use diesel::{insert_into, result::Error, QueryDsl};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
impl PersonPostAggregates { impl PersonPostAggregates {
@ -14,11 +21,13 @@ impl PersonPostAggregates {
form: &PersonPostAggregatesForm, form: &PersonPostAggregatesForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(person_post_aggregates) let form = (form, post_actions::read_comments.eq(now().nullable()));
insert_into(post_actions::table)
.values(form) .values(form)
.on_conflict((person_id, post_id)) .on_conflict((post_actions::person_id, post_actions::post_id))
.do_update() .do_update()
.set(form) .set(form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -28,8 +37,8 @@ impl PersonPostAggregates {
post_id_: PostId, post_id_: PostId,
) -> Result<Option<Self>, Error> { ) -> Result<Option<Self>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
person_post_aggregates find_action(post_actions::read_comments, (person_id_, post_id_))
.find((person_id_, post_id_)) .select(Self::as_select())
.first(conn) .first(conn)
.await .await
.optional() .optional()

View File

@ -4,12 +4,14 @@ use crate::schema::{
comment_aggregates, comment_aggregates,
community_aggregates, community_aggregates,
person_aggregates, person_aggregates,
person_post_aggregates, post_actions,
post_aggregates, post_aggregates,
site_aggregates, site_aggregates,
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
#[cfg(feature = "full")]
use i_love_jesus::CursorKeysModule; use i_love_jesus::CursorKeysModule;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "full")] #[cfg(feature = "full")]
@ -151,7 +153,7 @@ pub struct PostAggregates {
feature = "full", feature = "full",
derive(Queryable, Selectable, Associations, Identifiable) derive(Queryable, Selectable, Associations, Identifiable)
)] )]
#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))] #[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(primary_key(person_id, post_id)))]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
@ -162,18 +164,22 @@ pub struct PersonPostAggregates {
/// The number of comments they've read on that post. /// 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. /// 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, 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>, pub published: DateTime<Utc>,
} }
#[derive(Clone, Default)] #[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))]
pub struct PersonPostAggregatesForm { pub struct PersonPostAggregatesForm {
pub person_id: PersonId, pub person_id: PersonId,
pub post_id: PostId, pub post_id: PostId,
#[cfg_attr(feature = "full", diesel(column_name = read_comments_amount))]
pub read_comments: i64, pub read_comments: i64,
pub published: Option<DateTime<Utc>>,
} }
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy, Hash)] #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy, Hash)]

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
diesel::{DecoratableTarget, OptionalExtension}, diesel::{DecoratableTarget, OptionalExtension},
newtypes::{CommentId, DbUrl, PersonId}, newtypes::{CommentId, DbUrl, PersonId},
schema::comment, schema::{comment, comment_actions},
source::comment::{ source::comment::{
Comment, Comment,
CommentInsertForm, CommentInsertForm,
@ -12,10 +12,25 @@ use crate::{
CommentUpdateForm, CommentUpdateForm,
}, },
traits::{Crud, Likeable, Saveable}, traits::{Crud, Likeable, Saveable},
utils::{functions::coalesce, get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT}, utils::{
functions::coalesce,
get_conn,
naive_now,
now,
uplete,
DbPool,
DELETED_REPLACEMENT_TEXT,
},
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel::{
dsl::insert_into,
expression::SelectableHelper,
result::Error,
ExpressionMethods,
NullableExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use diesel_ltree::Ltree; use diesel_ltree::Ltree;
use url::Url; use url::Url;
@ -141,13 +156,17 @@ impl Likeable for CommentLike {
type Form = CommentLikeForm; type Form = CommentLikeForm;
type IdType = CommentId; type IdType = CommentId;
async fn like(pool: &mut DbPool<'_>, comment_like_form: &CommentLikeForm) -> Result<Self, Error> { async fn like(pool: &mut DbPool<'_>, comment_like_form: &CommentLikeForm) -> Result<Self, Error> {
use crate::schema::comment_like::dsl::{comment_id, comment_like, person_id};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(comment_like) let comment_like_form = (
comment_like_form,
comment_actions::liked.eq(now().nullable()),
);
insert_into(comment_actions::table)
.values(comment_like_form) .values(comment_like_form)
.on_conflict((comment_id, person_id)) .on_conflict((comment_actions::comment_id, comment_actions::person_id))
.do_update() .do_update()
.set(comment_like_form) .set(comment_like_form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -155,11 +174,15 @@ impl Likeable for CommentLike {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
person_id: PersonId, person_id: PersonId,
comment_id: CommentId, comment_id: CommentId,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
use crate::schema::comment_like::dsl::comment_like;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(comment_like.find((person_id, comment_id))) uplete::new(comment_actions::table.find((person_id, comment_id)))
.execute(conn) .set_null(comment_actions::like_score)
.set_null(comment_actions::liked)
// Deleting empty `comment_actions` rows would not work without setting `post_id` to
// null, because it's not part of the primary key
.set_null(comment_actions::post_id)
.get_result(conn)
.await .await
} }
} }
@ -171,26 +194,30 @@ impl Saveable for CommentSaved {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
comment_saved_form: &CommentSavedForm, comment_saved_form: &CommentSavedForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::comment_saved::dsl::{comment_id, comment_saved, person_id};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(comment_saved) let comment_saved_form = (
comment_saved_form,
comment_actions::saved.eq(now().nullable()),
);
insert_into(comment_actions::table)
.values(comment_saved_form) .values(comment_saved_form)
.on_conflict((comment_id, person_id)) .on_conflict((comment_actions::comment_id, comment_actions::person_id))
.do_update() .do_update()
.set(comment_saved_form) .set(comment_saved_form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn unsave( async fn unsave(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
comment_saved_form: &CommentSavedForm, comment_saved_form: &CommentSavedForm,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
use crate::schema::comment_saved::dsl::comment_saved;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete( uplete::new(
comment_saved.find((comment_saved_form.person_id, comment_saved_form.comment_id)), comment_actions::table.find((comment_saved_form.person_id, comment_saved_form.comment_id)),
) )
.execute(conn) .set_null(comment_actions::saved)
.get_result(conn)
.await .await
} }
} }
@ -218,7 +245,7 @@ mod tests {
post::{Post, PostInsertForm}, post::{Post, PostInsertForm},
}, },
traits::{Crud, Likeable, Saveable}, traits::{Crud, Likeable, Saveable},
utils::build_db_pool_for_tests, utils::{build_db_pool_for_tests, uplete},
}; };
use diesel_ltree::Ltree; use diesel_ltree::Ltree;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@ -367,8 +394,8 @@ mod tests {
format!("0.{}.{}", expected_comment.id, inserted_child_comment.id), format!("0.{}.{}", expected_comment.id, inserted_child_comment.id),
inserted_child_comment.path.0, inserted_child_comment.path.0,
); );
assert_eq!(1, like_removed); assert_eq!(uplete::Count::only_updated(1), like_removed);
assert_eq!(1, saved_removed); assert_eq!(uplete::Count::only_deleted(1), saved_removed);
assert_eq!(1, num_deleted); assert_eq!(1, num_deleted);
} }
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
diesel::{DecoratableTarget, OptionalExtension}, diesel::{DecoratableTarget, OptionalExtension},
newtypes::{CommunityId, DbUrl, PersonId}, newtypes::{CommunityId, DbUrl, PersonId},
schema::{community, community_follower, instance}, schema::{community, community_actions, instance},
source::{ source::{
actor_language::CommunityLanguage, actor_language::CommunityLanguage,
community::{ community::{
@ -19,8 +19,12 @@ use crate::{
}, },
traits::{ApubActor, Bannable, Crud, Followable, Joinable}, traits::{ApubActor, Bannable, Crud, Followable, Joinable},
utils::{ utils::{
action_query,
find_action,
functions::{coalesce, lower}, functions::{coalesce, lower},
get_conn, get_conn,
now,
uplete,
DbPool, DbPool,
}, },
SubscribedType, SubscribedType,
@ -30,6 +34,7 @@ use diesel::{
deserialize, deserialize,
dsl, dsl,
dsl::{exists, insert_into}, dsl::{exists, insert_into},
expression::SelectableHelper,
pg::Pg, pg::Pg,
result::Error, result::Error,
select, select,
@ -83,10 +88,20 @@ impl Joinable for CommunityModerator {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
community_moderator_form: &CommunityModeratorForm, community_moderator_form: &CommunityModeratorForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::community_moderator::dsl::community_moderator;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(community_moderator) let community_moderator_form = (
community_moderator_form,
community_actions::became_moderator.eq(now().nullable()),
);
insert_into(community_actions::table)
.values(community_moderator_form) .values(community_moderator_form)
.on_conflict((
community_actions::person_id,
community_actions::community_id,
))
.do_update()
.set(community_moderator_form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -94,14 +109,14 @@ impl Joinable for CommunityModerator {
async fn leave( async fn leave(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
community_moderator_form: &CommunityModeratorForm, community_moderator_form: &CommunityModeratorForm,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
use crate::schema::community_moderator::dsl::community_moderator;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(community_moderator.find(( uplete::new(community_actions::table.find((
community_moderator_form.person_id, community_moderator_form.person_id,
community_moderator_form.community_id, community_moderator_form.community_id,
))) )))
.execute(conn) .set_null(community_actions::became_moderator)
.get_result(conn)
.await .await
} }
} }
@ -199,23 +214,25 @@ impl CommunityModerator {
pub async fn delete_for_community( pub async fn delete_for_community(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
for_community_id: CommunityId, for_community_id: CommunityId,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
use crate::schema::community_moderator::dsl::{community_id, community_moderator};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(community_moderator.filter(community_id.eq(for_community_id))) uplete::new(
.execute(conn) community_actions::table.filter(community_actions::community_id.eq(for_community_id)),
.await )
.set_null(community_actions::became_moderator)
.get_result(conn)
.await
} }
pub async fn leave_all_communities( pub async fn leave_all_communities(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
for_person_id: PersonId, for_person_id: PersonId,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
use crate::schema::community_moderator::dsl::{community_moderator, person_id};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(community_moderator.filter(person_id.eq(for_person_id))) uplete::new(community_actions::table.filter(community_actions::person_id.eq(for_person_id)))
.execute(conn) .set_null(community_actions::became_moderator)
.get_result(conn)
.await .await
} }
@ -223,11 +240,10 @@ impl CommunityModerator {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
for_person_id: PersonId, for_person_id: PersonId,
) -> Result<Vec<CommunityId>, Error> { ) -> Result<Vec<CommunityId>, Error> {
use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
community_moderator action_query(community_actions::became_moderator)
.filter(person_id.eq(for_person_id)) .filter(community_actions::person_id.eq(for_person_id))
.select(community_id) .select(community_actions::community_id)
.load::<CommunityId>(conn) .load::<CommunityId>(conn)
.await .await
} }
@ -240,13 +256,20 @@ impl Bannable for CommunityPersonBan {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
community_person_ban_form: &CommunityPersonBanForm, community_person_ban_form: &CommunityPersonBanForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(community_person_ban) let community_person_ban_form = (
community_person_ban_form,
community_actions::received_ban.eq(now().nullable()),
);
insert_into(community_actions::table)
.values(community_person_ban_form) .values(community_person_ban_form)
.on_conflict((community_id, person_id)) .on_conflict((
community_actions::community_id,
community_actions::person_id,
))
.do_update() .do_update()
.set(community_person_ban_form) .set(community_person_ban_form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -254,14 +277,15 @@ impl Bannable for CommunityPersonBan {
async fn unban( async fn unban(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
community_person_ban_form: &CommunityPersonBanForm, community_person_ban_form: &CommunityPersonBanForm,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
use crate::schema::community_person_ban::dsl::community_person_ban;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(community_person_ban.find(( uplete::new(community_actions::table.find((
community_person_ban_form.person_id, community_person_ban_form.person_id,
community_person_ban_form.community_id, community_person_ban_form.community_id,
))) )))
.execute(conn) .set_null(community_actions::received_ban)
.set_null(community_actions::ban_expires)
.get_result(conn)
.await .await
} }
} }
@ -281,8 +305,8 @@ impl CommunityFollower {
} }
} }
pub fn select_subscribed_type() -> dsl::Nullable<community_follower::pending> { pub fn select_subscribed_type() -> dsl::Nullable<community_actions::follow_pending> {
community_follower::pending.nullable() community_actions::follow_pending.nullable()
} }
/// Check if a remote instance has any followers on local instance. For this it is enough to check /// Check if a remote instance has any followers on local instance. For this it is enough to check
@ -291,10 +315,10 @@ impl CommunityFollower {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
remote_community_id: CommunityId, remote_community_id: CommunityId,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
use crate::schema::community_follower::dsl::{community_follower, community_id};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
select(exists( select(exists(
community_follower.filter(community_id.eq(remote_community_id)), action_query(community_actions::followed)
.filter(community_actions::community_id.eq(remote_community_id)),
)) ))
.get_result(conn) .get_result(conn)
.await .await
@ -316,13 +340,17 @@ impl Queryable<sql_types::Nullable<sql_types::Bool>, Pg> for SubscribedType {
impl Followable for CommunityFollower { impl Followable for CommunityFollower {
type Form = CommunityFollowerForm; type Form = CommunityFollowerForm;
async fn follow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<Self, Error> { async fn follow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<Self, Error> {
use crate::schema::community_follower::dsl::{community_follower, community_id, person_id};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(community_follower) let form = (form, community_actions::followed.eq(now().nullable()));
insert_into(community_actions::table)
.values(form) .values(form)
.on_conflict((community_id, person_id)) .on_conflict((
community_actions::community_id,
community_actions::person_id,
))
.do_update() .do_update()
.set(form) .set(form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -331,18 +359,25 @@ impl Followable for CommunityFollower {
community_id: CommunityId, community_id: CommunityId,
person_id: PersonId, person_id: PersonId,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
use crate::schema::community_follower::dsl::{community_follower, pending};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::update(community_follower.find((person_id, community_id))) diesel::update(find_action(
.set(pending.eq(false)) community_actions::follow_pending,
.get_result::<Self>(conn) (person_id, community_id),
.await ))
.set(community_actions::follow_pending.eq(Some(false)))
.returning(Self::as_select())
.get_result::<Self>(conn)
.await
} }
async fn unfollow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result<usize, Error> { async fn unfollow(
use crate::schema::community_follower::dsl::community_follower; pool: &mut DbPool<'_>,
form: &CommunityFollowerForm,
) -> Result<uplete::Count, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(community_follower.find((form.person_id, form.community_id))) uplete::new(community_actions::table.find((form.person_id, form.community_id)))
.execute(conn) .set_null(community_actions::followed)
.set_null(community_actions::follow_pending)
.get_result(conn)
.await .await
} }
} }
@ -418,7 +453,7 @@ mod tests {
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
}, },
traits::{Bannable, Crud, Followable, Joinable}, traits::{Bannable, Crud, Followable, Joinable},
utils::build_db_pool_for_tests, utils::{build_db_pool_for_tests, uplete},
CommunityVisibility, CommunityVisibility,
}; };
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@ -558,9 +593,9 @@ mod tests {
assert_eq!(expected_community_follower, inserted_community_follower); assert_eq!(expected_community_follower, inserted_community_follower);
assert_eq!(expected_community_moderator, inserted_community_moderator); assert_eq!(expected_community_moderator, inserted_community_moderator);
assert_eq!(expected_community_person_ban, inserted_community_person_ban); assert_eq!(expected_community_person_ban, inserted_community_person_ban);
assert_eq!(1, ignored_community); assert_eq!(uplete::Count::only_updated(1), ignored_community);
assert_eq!(1, left_community); assert_eq!(uplete::Count::only_updated(1), left_community);
assert_eq!(1, unban); assert_eq!(uplete::Count::only_deleted(1), unban);
// assert_eq!(2, loaded_count); // assert_eq!(2, loaded_count);
assert_eq!(1, num_deleted); assert_eq!(1, num_deleted);
} }

View File

@ -1,14 +1,17 @@
use crate::{ use crate::{
newtypes::{CommunityId, PersonId}, newtypes::{CommunityId, PersonId},
schema::community_block::dsl::{community_block, community_id, person_id}, schema::community_actions,
source::community_block::{CommunityBlock, CommunityBlockForm}, source::community_block::{CommunityBlock, CommunityBlockForm},
traits::Blockable, traits::Blockable,
utils::{get_conn, DbPool}, utils::{find_action, get_conn, now, uplete, DbPool},
}; };
use diesel::{ use diesel::{
dsl::{exists, insert_into}, dsl::{exists, insert_into},
expression::SelectableHelper,
result::Error, result::Error,
select, select,
ExpressionMethods,
NullableExpressionMethods,
QueryDsl, QueryDsl,
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
@ -20,9 +23,10 @@ impl CommunityBlock {
for_community_id: CommunityId, for_community_id: CommunityId,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
select(exists( select(exists(find_action(
community_block.find((for_person_id, for_community_id)), community_actions::blocked,
)) (for_person_id, for_community_id),
)))
.get_result(conn) .get_result(conn)
.await .await
} }
@ -33,24 +37,33 @@ impl Blockable for CommunityBlock {
type Form = CommunityBlockForm; type Form = CommunityBlockForm;
async fn block(pool: &mut DbPool<'_>, community_block_form: &Self::Form) -> Result<Self, Error> { async fn block(pool: &mut DbPool<'_>, community_block_form: &Self::Form) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(community_block) let community_block_form = (
community_block_form,
community_actions::blocked.eq(now().nullable()),
);
insert_into(community_actions::table)
.values(community_block_form) .values(community_block_form)
.on_conflict((person_id, community_id)) .on_conflict((
community_actions::person_id,
community_actions::community_id,
))
.do_update() .do_update()
.set(community_block_form) .set(community_block_form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn unblock( async fn unblock(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
community_block_form: &Self::Form, community_block_form: &Self::Form,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(community_block.find(( uplete::new(community_actions::table.find((
community_block_form.person_id, community_block_form.person_id,
community_block_form.community_id, community_block_form.community_id,
))) )))
.execute(conn) .set_null(community_actions::blocked)
.get_result(conn)
.await .await
} }
} }

View File

@ -1,14 +1,17 @@
use crate::{ use crate::{
newtypes::{InstanceId, PersonId}, newtypes::{InstanceId, PersonId},
schema::instance_block::dsl::{instance_block, instance_id, person_id}, schema::instance_actions,
source::instance_block::{InstanceBlock, InstanceBlockForm}, source::instance_block::{InstanceBlock, InstanceBlockForm},
traits::Blockable, traits::Blockable,
utils::{get_conn, DbPool}, utils::{find_action, get_conn, now, uplete, DbPool},
}; };
use diesel::{ use diesel::{
dsl::{exists, insert_into}, dsl::{exists, insert_into},
expression::SelectableHelper,
result::Error, result::Error,
select, select,
ExpressionMethods,
NullableExpressionMethods,
QueryDsl, QueryDsl,
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
@ -20,9 +23,10 @@ impl InstanceBlock {
for_instance_id: InstanceId, for_instance_id: InstanceId,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
select(exists( select(exists(find_action(
instance_block.find((for_person_id, for_instance_id)), instance_actions::blocked,
)) (for_person_id, for_instance_id),
)))
.get_result(conn) .get_result(conn)
.await .await
} }
@ -33,24 +37,30 @@ impl Blockable for InstanceBlock {
type Form = InstanceBlockForm; type Form = InstanceBlockForm;
async fn block(pool: &mut DbPool<'_>, instance_block_form: &Self::Form) -> Result<Self, Error> { async fn block(pool: &mut DbPool<'_>, instance_block_form: &Self::Form) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(instance_block) let instance_block_form = (
instance_block_form,
instance_actions::blocked.eq(now().nullable()),
);
insert_into(instance_actions::table)
.values(instance_block_form) .values(instance_block_form)
.on_conflict((person_id, instance_id)) .on_conflict((instance_actions::person_id, instance_actions::instance_id))
.do_update() .do_update()
.set(instance_block_form) .set(instance_block_form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn unblock( async fn unblock(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
instance_block_form: &Self::Form, instance_block_form: &Self::Form,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(instance_block.find(( uplete::new(instance_actions::table.find((
instance_block_form.person_id, instance_block_form.person_id,
instance_block_form.instance_id, instance_block_form.instance_id,
))) )))
.execute(conn) .set_null(instance_actions::blocked)
.get_result(conn)
.await .await
} }
} }

View File

@ -8,6 +8,7 @@ use crate::{
site::Site, site::Site,
}, },
utils::{ utils::{
action_query,
functions::{coalesce, lower}, functions::{coalesce, lower},
get_conn, get_conn,
now, now,
@ -150,55 +151,54 @@ impl LocalUser {
) -> Result<UserBackupLists, Error> { ) -> Result<UserBackupLists, Error> {
use crate::schema::{ use crate::schema::{
comment, comment,
comment_saved, comment_actions,
community, community,
community_block, community_actions,
community_follower,
instance, instance,
instance_block, instance_actions,
person_block, person_actions,
post, post,
post_saved, post_actions,
}; };
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let followed_communities = community_follower::dsl::community_follower let followed_communities = action_query(community_actions::followed)
.filter(community_follower::person_id.eq(person_id_)) .filter(community_actions::person_id.eq(person_id_))
.inner_join(community::table.on(community_follower::community_id.eq(community::id)))
.select(community::actor_id)
.get_results(conn)
.await?;
let saved_posts = post_saved::dsl::post_saved
.filter(post_saved::person_id.eq(person_id_))
.inner_join(post::table.on(post_saved::post_id.eq(post::id)))
.select(post::ap_id)
.get_results(conn)
.await?;
let saved_comments = comment_saved::dsl::comment_saved
.filter(comment_saved::person_id.eq(person_id_))
.inner_join(comment::table.on(comment_saved::comment_id.eq(comment::id)))
.select(comment::ap_id)
.get_results(conn)
.await?;
let blocked_communities = community_block::dsl::community_block
.filter(community_block::person_id.eq(person_id_))
.inner_join(community::table) .inner_join(community::table)
.select(community::actor_id) .select(community::actor_id)
.get_results(conn) .get_results(conn)
.await?; .await?;
let blocked_users = person_block::dsl::person_block let saved_posts = action_query(post_actions::saved)
.filter(person_block::person_id.eq(person_id_)) .filter(post_actions::person_id.eq(person_id_))
.inner_join(person::table.on(person_block::target_id.eq(person::id))) .inner_join(post::table)
.select(post::ap_id)
.get_results(conn)
.await?;
let saved_comments = action_query(comment_actions::saved)
.filter(comment_actions::person_id.eq(person_id_))
.inner_join(comment::table)
.select(comment::ap_id)
.get_results(conn)
.await?;
let blocked_communities = action_query(community_actions::blocked)
.filter(community_actions::person_id.eq(person_id_))
.inner_join(community::table)
.select(community::actor_id)
.get_results(conn)
.await?;
let blocked_users = action_query(person_actions::blocked)
.filter(person_actions::person_id.eq(person_id_))
.inner_join(person::table.on(person_actions::target_id.eq(person::id)))
.select(person::actor_id) .select(person::actor_id)
.get_results(conn) .get_results(conn)
.await?; .await?;
let blocked_instances = instance_block::dsl::instance_block let blocked_instances = action_query(instance_actions::blocked)
.filter(instance_block::person_id.eq(person_id_)) .filter(instance_actions::person_id.eq(person_id_))
.inner_join(instance::table) .inner_join(instance::table)
.select(instance::domain) .select(instance::domain)
.get_results(conn) .get_results(conn)

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
diesel::OptionalExtension, diesel::OptionalExtension,
newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, newtypes::{CommunityId, DbUrl, InstanceId, PersonId},
schema::{comment, community, instance, local_user, person, person_follower, post}, schema::{comment, community, instance, local_user, person, person_actions, post},
source::person::{ source::person::{
Person, Person,
PersonFollower, PersonFollower,
@ -10,14 +10,16 @@ use crate::{
PersonUpdateForm, PersonUpdateForm,
}, },
traits::{ApubActor, Crud, Followable}, traits::{ApubActor, Crud, Followable},
utils::{functions::lower, get_conn, naive_now, DbPool}, utils::{action_query, functions::lower, get_conn, naive_now, now, uplete, DbPool},
}; };
use diesel::{ use diesel::{
dsl::{insert_into, not}, dsl::{insert_into, not},
expression::SelectableHelper,
result::Error, result::Error,
CombineDsl, CombineDsl,
ExpressionMethods, ExpressionMethods,
JoinOnDsl, JoinOnDsl,
NullableExpressionMethods,
QueryDsl, QueryDsl,
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
@ -182,24 +184,29 @@ impl ApubActor for Person {
impl Followable for PersonFollower { impl Followable for PersonFollower {
type Form = PersonFollowerForm; type Form = PersonFollowerForm;
async fn follow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result<Self, Error> { async fn follow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result<Self, Error> {
use crate::schema::person_follower::dsl::{follower_id, person_follower, person_id};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(person_follower) let form = (form, person_actions::followed.eq(now().nullable()));
insert_into(person_actions::table)
.values(form) .values(form)
.on_conflict((follower_id, person_id)) .on_conflict((person_actions::person_id, person_actions::target_id))
.do_update() .do_update()
.set(form) .set(form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn follow_accepted(_: &mut DbPool<'_>, _: CommunityId, _: PersonId) -> Result<Self, Error> { async fn follow_accepted(_: &mut DbPool<'_>, _: CommunityId, _: PersonId) -> Result<Self, Error> {
unimplemented!() unimplemented!()
} }
async fn unfollow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result<usize, Error> { async fn unfollow(
use crate::schema::person_follower::dsl::person_follower; pool: &mut DbPool<'_>,
form: &PersonFollowerForm,
) -> Result<uplete::Count, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(person_follower.find((form.follower_id, form.person_id))) uplete::new(person_actions::table.find((form.follower_id, form.person_id)))
.execute(conn) .set_null(person_actions::followed)
.set_null(person_actions::follow_pending)
.get_result(conn)
.await .await
} }
} }
@ -210,9 +217,9 @@ impl PersonFollower {
for_person_id: PersonId, for_person_id: PersonId,
) -> Result<Vec<Person>, Error> { ) -> Result<Vec<Person>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
person_follower::table action_query(person_actions::followed)
.inner_join(person::table.on(person_follower::follower_id.eq(person::id))) .inner_join(person::table.on(person_actions::person_id.eq(person::id)))
.filter(person_follower::person_id.eq(for_person_id)) .filter(person_actions::target_id.eq(for_person_id))
.select(person::all_columns) .select(person::all_columns)
.load(conn) .load(conn)
.await .await
@ -230,7 +237,7 @@ mod tests {
person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm}, person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm},
}, },
traits::{Crud, Followable}, traits::{Crud, Followable},
utils::build_db_pool_for_tests, utils::{build_db_pool_for_tests, uplete},
}; };
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
@ -325,6 +332,6 @@ mod tests {
assert_eq!(vec![person_2], followers); assert_eq!(vec![person_2], followers);
let unfollow = PersonFollower::unfollow(pool, &follow_form).await.unwrap(); let unfollow = PersonFollower::unfollow(pool, &follow_form).await.unwrap();
assert_eq!(1, unfollow); assert_eq!(uplete::Count::only_deleted(1), unfollow);
} }
} }

View File

@ -1,14 +1,17 @@
use crate::{ use crate::{
newtypes::PersonId, newtypes::PersonId,
schema::person_block::dsl::{person_block, person_id, target_id}, schema::person_actions,
source::person_block::{PersonBlock, PersonBlockForm}, source::person_block::{PersonBlock, PersonBlockForm},
traits::Blockable, traits::Blockable,
utils::{get_conn, DbPool}, utils::{find_action, get_conn, now, uplete, DbPool},
}; };
use diesel::{ use diesel::{
dsl::{exists, insert_into}, dsl::{exists, insert_into},
expression::SelectableHelper,
result::Error, result::Error,
select, select,
ExpressionMethods,
NullableExpressionMethods,
QueryDsl, QueryDsl,
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
@ -20,9 +23,12 @@ impl PersonBlock {
for_recipient_id: PersonId, for_recipient_id: PersonId,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
select(exists(person_block.find((for_person_id, for_recipient_id)))) select(exists(find_action(
.get_result(conn) person_actions::blocked,
.await (for_person_id, for_recipient_id),
)))
.get_result(conn)
.await
} }
} }
@ -34,18 +40,29 @@ impl Blockable for PersonBlock {
person_block_form: &PersonBlockForm, person_block_form: &PersonBlockForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(person_block) let person_block_form = (
person_block_form,
person_actions::blocked.eq(now().nullable()),
);
insert_into(person_actions::table)
.values(person_block_form) .values(person_block_form)
.on_conflict((person_id, target_id)) .on_conflict((person_actions::person_id, person_actions::target_id))
.do_update() .do_update()
.set(person_block_form) .set(person_block_form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn unblock(pool: &mut DbPool<'_>, person_block_form: &Self::Form) -> Result<usize, Error> { async fn unblock(
pool: &mut DbPool<'_>,
person_block_form: &Self::Form,
) -> Result<uplete::Count, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(person_block.find((person_block_form.person_id, person_block_form.target_id))) uplete::new(
.execute(conn) person_actions::table.find((person_block_form.person_id, person_block_form.target_id)),
.await )
.set_null(person_actions::blocked)
.get_result(conn)
.await
} }
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
diesel::OptionalExtension, diesel::OptionalExtension,
newtypes::{CommunityId, DbUrl, PersonId, PostId}, newtypes::{CommunityId, DbUrl, PersonId, PostId},
schema::{post, post_hide, post_like, post_read, post_saved}, schema::{post, post_actions},
source::post::{ source::post::{
Post, Post,
PostHide, PostHide,
@ -20,6 +20,8 @@ use crate::{
functions::coalesce, functions::coalesce,
get_conn, get_conn,
naive_now, naive_now,
now,
uplete,
DbPool, DbPool,
DELETED_REPLACEMENT_TEXT, DELETED_REPLACEMENT_TEXT,
FETCH_LIMIT_MAX, FETCH_LIMIT_MAX,
@ -31,9 +33,11 @@ use ::url::Url;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use diesel::{ use diesel::{
dsl::insert_into, dsl::insert_into,
expression::SelectableHelper,
result::Error, result::Error,
DecoratableTarget, DecoratableTarget,
ExpressionMethods, ExpressionMethods,
NullableExpressionMethods,
QueryDsl, QueryDsl,
TextExpressionMethods, TextExpressionMethods,
}; };
@ -250,11 +254,13 @@ impl Likeable for PostLike {
type IdType = PostId; type IdType = PostId;
async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result<Self, Error> { async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(post_like::table) let post_like_form = (post_like_form, post_actions::liked.eq(now().nullable()));
insert_into(post_actions::table)
.values(post_like_form) .values(post_like_form)
.on_conflict((post_like::post_id, post_like::person_id)) .on_conflict((post_actions::post_id, post_actions::person_id))
.do_update() .do_update()
.set(post_like_form) .set(post_like_form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -262,10 +268,12 @@ impl Likeable for PostLike {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
person_id: PersonId, person_id: PersonId,
post_id: PostId, post_id: PostId,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(post_like::table.find((person_id, post_id))) uplete::new(post_actions::table.find((person_id, post_id)))
.execute(conn) .set_null(post_actions::like_score)
.set_null(post_actions::liked)
.get_result(conn)
.await .await
} }
} }
@ -275,18 +283,24 @@ impl Saveable for PostSaved {
type Form = PostSavedForm; type Form = PostSavedForm;
async fn save(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result<Self, Error> { async fn save(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(post_saved::table) let post_saved_form = (post_saved_form, post_actions::saved.eq(now().nullable()));
insert_into(post_actions::table)
.values(post_saved_form) .values(post_saved_form)
.on_conflict((post_saved::post_id, post_saved::person_id)) .on_conflict((post_actions::post_id, post_actions::person_id))
.do_update() .do_update()
.set(post_saved_form) .set(post_saved_form)
.returning(Self::as_select())
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn unsave(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result<usize, Error> { async fn unsave(
pool: &mut DbPool<'_>,
post_saved_form: &PostSavedForm,
) -> Result<uplete::Count, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete(post_saved::table.find((post_saved_form.person_id, post_saved_form.post_id))) uplete::new(post_actions::table.find((post_saved_form.person_id, post_saved_form.post_id)))
.execute(conn) .set_null(post_actions::saved)
.get_result(conn)
.await .await
} }
} }
@ -301,11 +315,18 @@ impl PostRead {
let forms = post_ids let forms = post_ids
.into_iter() .into_iter()
.map(|post_id| PostReadForm { post_id, person_id }) .map(|post_id| {
.collect::<Vec<PostReadForm>>(); (
insert_into(post_read::table) PostReadForm { post_id, person_id },
post_actions::read.eq(now().nullable()),
)
})
.collect::<Vec<_>>();
insert_into(post_actions::table)
.values(forms) .values(forms)
.on_conflict_do_nothing() .on_conflict((post_actions::person_id, post_actions::post_id))
.do_update()
.set(post_actions::read.eq(now().nullable()))
.execute(conn) .execute(conn)
.await .await
} }
@ -314,15 +335,16 @@ impl PostRead {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
post_id_: HashSet<PostId>, post_id_: HashSet<PostId>,
person_id_: PersonId, person_id_: PersonId,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete( uplete::new(
post_read::table post_actions::table
.filter(post_read::post_id.eq_any(post_id_)) .filter(post_actions::post_id.eq_any(post_id_))
.filter(post_read::person_id.eq(person_id_)), .filter(post_actions::person_id.eq(person_id_)),
) )
.execute(conn) .set_null(post_actions::read)
.get_result(conn)
.await .await
} }
} }
@ -337,11 +359,18 @@ impl PostHide {
let forms = post_ids let forms = post_ids
.into_iter() .into_iter()
.map(|post_id| PostHideForm { post_id, person_id }) .map(|post_id| {
.collect::<Vec<PostHideForm>>(); (
insert_into(post_hide::table) PostHideForm { post_id, person_id },
post_actions::hidden.eq(now().nullable()),
)
})
.collect::<Vec<_>>();
insert_into(post_actions::table)
.values(forms) .values(forms)
.on_conflict_do_nothing() .on_conflict((post_actions::person_id, post_actions::post_id))
.do_update()
.set(post_actions::hidden.eq(now().nullable()))
.execute(conn) .execute(conn)
.await .await
} }
@ -350,15 +379,16 @@ impl PostHide {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
post_id_: HashSet<PostId>, post_id_: HashSet<PostId>,
person_id_: PersonId, person_id_: PersonId,
) -> Result<usize, Error> { ) -> Result<uplete::Count, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::delete( uplete::new(
post_hide::table post_actions::table
.filter(post_hide::post_id.eq_any(post_id_)) .filter(post_actions::post_id.eq_any(post_id_))
.filter(post_hide::person_id.eq(person_id_)), .filter(post_actions::person_id.eq(person_id_)),
) )
.execute(conn) .set_null(post_actions::hidden)
.get_result(conn)
.await .await
} }
} }
@ -385,7 +415,7 @@ mod tests {
}, },
}, },
traits::{Crud, Likeable, Saveable}, traits::{Crud, Likeable, Saveable},
utils::build_db_pool_for_tests, utils::{build_db_pool_for_tests, uplete},
}; };
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
@ -511,9 +541,9 @@ mod tests {
let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id) let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id)
.await .await
.unwrap(); .unwrap();
assert_eq!(1, like_removed); assert_eq!(uplete::Count::only_updated(1), like_removed);
let saved_removed = PostSaved::unsave(pool, &post_saved_form).await.unwrap(); let saved_removed = PostSaved::unsave(pool, &post_saved_form).await.unwrap();
assert_eq!(1, saved_removed); assert_eq!(uplete::Count::only_updated(1), saved_removed);
let read_removed = PostRead::mark_as_unread( let read_removed = PostRead::mark_as_unread(
pool, pool,
HashSet::from([inserted_post.id, inserted_post2.id]), HashSet::from([inserted_post.id, inserted_post2.id]),
@ -521,7 +551,7 @@ mod tests {
) )
.await .await
.unwrap(); .unwrap();
assert_eq!(2, read_removed); assert_eq!(uplete::Count::only_deleted(2), read_removed);
let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap() let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap()
+ Post::delete(pool, inserted_post2.id).await.unwrap(); + Post::delete(pool, inserted_post2.id).await.unwrap();

View File

@ -31,11 +31,11 @@ pub mod sensitive;
pub mod schema; pub mod schema;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod aliases { pub mod aliases {
use crate::schema::{community_moderator, person}; use crate::schema::{community_actions, person};
diesel::alias!( diesel::alias!(
community_actions as creator_community_actions: CreatorCommunityActions,
person as person1: Person1, person as person1: Person1,
person as person2: Person2, person as person2: Person2,
community_moderator as community_moderator1: CommunityModerator1
); );
} }
pub mod source; pub mod source;

View File

@ -98,6 +98,17 @@ diesel::table! {
} }
} }
diesel::table! {
comment_actions (person_id, comment_id) {
person_id -> Int4,
comment_id -> Int4,
post_id -> Nullable<Int4>,
like_score -> Nullable<Int2>,
liked -> Nullable<Timestamptz>,
saved -> Nullable<Timestamptz>,
}
}
diesel::table! { diesel::table! {
comment_aggregates (comment_id) { comment_aggregates (comment_id) {
comment_id -> Int4, comment_id -> Int4,
@ -111,16 +122,6 @@ diesel::table! {
} }
} }
diesel::table! {
comment_like (person_id, comment_id) {
person_id -> Int4,
comment_id -> Int4,
post_id -> Int4,
score -> Int2,
published -> Timestamptz,
}
}
diesel::table! { diesel::table! {
comment_reply (id) { comment_reply (id) {
id -> Int4, id -> Int4,
@ -145,14 +146,6 @@ diesel::table! {
} }
} }
diesel::table! {
comment_saved (person_id, comment_id) {
comment_id -> Int4,
person_id -> Int4,
published -> Timestamptz,
}
}
diesel::table! { diesel::table! {
use diesel::sql_types::*; use diesel::sql_types::*;
use super::sql_types::CommunityVisibility; use super::sql_types::CommunityVisibility;
@ -194,6 +187,19 @@ diesel::table! {
} }
} }
diesel::table! {
community_actions (person_id, community_id) {
community_id -> Int4,
person_id -> Int4,
followed -> Nullable<Timestamptz>,
follow_pending -> Nullable<Bool>,
blocked -> Nullable<Timestamptz>,
became_moderator -> Nullable<Timestamptz>,
received_ban -> Nullable<Timestamptz>,
ban_expires -> Nullable<Timestamptz>,
}
}
diesel::table! { diesel::table! {
community_aggregates (community_id) { community_aggregates (community_id) {
community_id -> Int4, community_id -> Int4,
@ -210,23 +216,6 @@ diesel::table! {
} }
} }
diesel::table! {
community_block (person_id, community_id) {
person_id -> Int4,
community_id -> Int4,
published -> Timestamptz,
}
}
diesel::table! {
community_follower (person_id, community_id) {
community_id -> Int4,
person_id -> Int4,
published -> Timestamptz,
pending -> Bool,
}
}
diesel::table! { diesel::table! {
community_language (community_id, language_id) { community_language (community_id, language_id) {
community_id -> Int4, community_id -> Int4,
@ -234,23 +223,6 @@ diesel::table! {
} }
} }
diesel::table! {
community_moderator (person_id, community_id) {
community_id -> Int4,
person_id -> Int4,
published -> Timestamptz,
}
}
diesel::table! {
community_person_ban (person_id, community_id) {
community_id -> Int4,
person_id -> Int4,
published -> Timestamptz,
expires -> Nullable<Timestamptz>,
}
}
diesel::table! { diesel::table! {
custom_emoji (id) { custom_emoji (id) {
id -> Int4, id -> Int4,
@ -333,10 +305,10 @@ diesel::table! {
} }
diesel::table! { diesel::table! {
instance_block (person_id, instance_id) { instance_actions (person_id, instance_id) {
person_id -> Int4, person_id -> Int4,
instance_id -> Int4, instance_id -> Int4,
published -> Timestamptz, blocked -> Nullable<Timestamptz>,
} }
} }
@ -653,6 +625,16 @@ diesel::table! {
} }
} }
diesel::table! {
person_actions (person_id, target_id) {
target_id -> Int4,
person_id -> Int4,
followed -> Nullable<Timestamptz>,
follow_pending -> Nullable<Bool>,
blocked -> Nullable<Timestamptz>,
}
}
diesel::table! { diesel::table! {
person_aggregates (person_id) { person_aggregates (person_id) {
person_id -> Int4, person_id -> Int4,
@ -670,23 +652,6 @@ diesel::table! {
} }
} }
diesel::table! {
person_block (person_id, target_id) {
person_id -> Int4,
target_id -> Int4,
published -> Timestamptz,
}
}
diesel::table! {
person_follower (follower_id, person_id) {
person_id -> Int4,
follower_id -> Int4,
published -> Timestamptz,
pending -> Bool,
}
}
diesel::table! { diesel::table! {
person_mention (id) { person_mention (id) {
id -> Int4, id -> Int4,
@ -697,15 +662,6 @@ diesel::table! {
} }
} }
diesel::table! {
person_post_aggregates (person_id, post_id) {
person_id -> Int4,
post_id -> Int4,
read_comments -> Int8,
published -> Timestamptz,
}
}
diesel::table! { diesel::table! {
post (id) { post (id) {
id -> Int4, id -> Int4,
@ -737,6 +693,20 @@ diesel::table! {
} }
} }
diesel::table! {
post_actions (person_id, post_id) {
post_id -> Int4,
person_id -> Int4,
read -> Nullable<Timestamptz>,
read_comments -> Nullable<Timestamptz>,
read_comments_amount -> Nullable<Int8>,
saved -> Nullable<Timestamptz>,
liked -> Nullable<Timestamptz>,
like_score -> Nullable<Int2>,
hidden -> Nullable<Timestamptz>,
}
}
diesel::table! { diesel::table! {
post_aggregates (post_id) { post_aggregates (post_id) {
post_id -> Int4, post_id -> Int4,
@ -759,31 +729,6 @@ diesel::table! {
} }
} }
diesel::table! {
post_hide (person_id, post_id) {
post_id -> Int4,
person_id -> Int4,
published -> Timestamptz,
}
}
diesel::table! {
post_like (person_id, post_id) {
post_id -> Int4,
person_id -> Int4,
score -> Int2,
published -> Timestamptz,
}
}
diesel::table! {
post_read (person_id, post_id) {
post_id -> Int4,
person_id -> Int4,
published -> Timestamptz,
}
}
diesel::table! { diesel::table! {
post_report (id) { post_report (id) {
id -> Int4, id -> Int4,
@ -801,14 +746,6 @@ diesel::table! {
} }
} }
diesel::table! {
post_saved (person_id, post_id) {
post_id -> Int4,
person_id -> Int4,
published -> Timestamptz,
}
}
diesel::table! { diesel::table! {
private_message (id) { private_message (id) {
id -> Int4, id -> Int4,
@ -953,35 +890,27 @@ diesel::joinable!(admin_purge_post -> person (admin_person_id));
diesel::joinable!(comment -> language (language_id)); diesel::joinable!(comment -> language (language_id));
diesel::joinable!(comment -> person (creator_id)); diesel::joinable!(comment -> person (creator_id));
diesel::joinable!(comment -> post (post_id)); diesel::joinable!(comment -> post (post_id));
diesel::joinable!(comment_actions -> comment (comment_id));
diesel::joinable!(comment_actions -> person (person_id));
diesel::joinable!(comment_actions -> post (post_id));
diesel::joinable!(comment_aggregates -> comment (comment_id)); diesel::joinable!(comment_aggregates -> comment (comment_id));
diesel::joinable!(comment_like -> comment (comment_id));
diesel::joinable!(comment_like -> person (person_id));
diesel::joinable!(comment_like -> post (post_id));
diesel::joinable!(comment_reply -> comment (comment_id)); diesel::joinable!(comment_reply -> comment (comment_id));
diesel::joinable!(comment_reply -> person (recipient_id)); diesel::joinable!(comment_reply -> person (recipient_id));
diesel::joinable!(comment_report -> comment (comment_id)); diesel::joinable!(comment_report -> comment (comment_id));
diesel::joinable!(comment_saved -> comment (comment_id));
diesel::joinable!(comment_saved -> person (person_id));
diesel::joinable!(community -> instance (instance_id)); diesel::joinable!(community -> instance (instance_id));
diesel::joinable!(community_actions -> community (community_id));
diesel::joinable!(community_actions -> person (person_id));
diesel::joinable!(community_aggregates -> community (community_id)); diesel::joinable!(community_aggregates -> community (community_id));
diesel::joinable!(community_block -> community (community_id));
diesel::joinable!(community_block -> person (person_id));
diesel::joinable!(community_follower -> community (community_id));
diesel::joinable!(community_follower -> person (person_id));
diesel::joinable!(community_language -> community (community_id)); diesel::joinable!(community_language -> community (community_id));
diesel::joinable!(community_language -> language (language_id)); diesel::joinable!(community_language -> language (language_id));
diesel::joinable!(community_moderator -> community (community_id));
diesel::joinable!(community_moderator -> person (person_id));
diesel::joinable!(community_person_ban -> community (community_id));
diesel::joinable!(community_person_ban -> person (person_id));
diesel::joinable!(custom_emoji -> local_site (local_site_id)); diesel::joinable!(custom_emoji -> local_site (local_site_id));
diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id)); diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id));
diesel::joinable!(email_verification -> local_user (local_user_id)); diesel::joinable!(email_verification -> local_user (local_user_id));
diesel::joinable!(federation_allowlist -> instance (instance_id)); diesel::joinable!(federation_allowlist -> instance (instance_id));
diesel::joinable!(federation_blocklist -> instance (instance_id)); diesel::joinable!(federation_blocklist -> instance (instance_id));
diesel::joinable!(federation_queue_state -> instance (instance_id)); diesel::joinable!(federation_queue_state -> instance (instance_id));
diesel::joinable!(instance_block -> instance (instance_id)); diesel::joinable!(instance_actions -> instance (instance_id));
diesel::joinable!(instance_block -> person (person_id)); diesel::joinable!(instance_actions -> person (person_id));
diesel::joinable!(local_image -> local_user (local_user_id)); diesel::joinable!(local_image -> local_user (local_user_id));
diesel::joinable!(local_site -> site (site_id)); diesel::joinable!(local_site -> site (site_id));
diesel::joinable!(local_site_rate_limit -> local_site (local_site_id)); diesel::joinable!(local_site_rate_limit -> local_site (local_site_id));
@ -1011,24 +940,16 @@ diesel::joinable!(person_aggregates -> person (person_id));
diesel::joinable!(person_ban -> person (person_id)); diesel::joinable!(person_ban -> person (person_id));
diesel::joinable!(person_mention -> comment (comment_id)); diesel::joinable!(person_mention -> comment (comment_id));
diesel::joinable!(person_mention -> person (recipient_id)); diesel::joinable!(person_mention -> person (recipient_id));
diesel::joinable!(person_post_aggregates -> person (person_id));
diesel::joinable!(person_post_aggregates -> post (post_id));
diesel::joinable!(post -> community (community_id)); diesel::joinable!(post -> community (community_id));
diesel::joinable!(post -> language (language_id)); diesel::joinable!(post -> language (language_id));
diesel::joinable!(post -> person (creator_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 -> community (community_id));
diesel::joinable!(post_aggregates -> instance (instance_id)); diesel::joinable!(post_aggregates -> instance (instance_id));
diesel::joinable!(post_aggregates -> person (creator_id)); diesel::joinable!(post_aggregates -> person (creator_id));
diesel::joinable!(post_aggregates -> post (post_id)); diesel::joinable!(post_aggregates -> post (post_id));
diesel::joinable!(post_hide -> person (person_id));
diesel::joinable!(post_hide -> post (post_id));
diesel::joinable!(post_like -> person (person_id));
diesel::joinable!(post_like -> post (post_id));
diesel::joinable!(post_read -> person (person_id));
diesel::joinable!(post_read -> post (post_id));
diesel::joinable!(post_report -> post (post_id)); diesel::joinable!(post_report -> post (post_id));
diesel::joinable!(post_saved -> person (person_id));
diesel::joinable!(post_saved -> post (post_id));
diesel::joinable!(private_message_report -> private_message (private_message_id)); diesel::joinable!(private_message_report -> private_message (private_message_id));
diesel::joinable!(registration_application -> local_user (local_user_id)); diesel::joinable!(registration_application -> local_user (local_user_id));
diesel::joinable!(registration_application -> person (admin_id)); diesel::joinable!(registration_application -> person (admin_id));
@ -1045,18 +966,14 @@ diesel::allow_tables_to_appear_in_same_query!(
admin_purge_post, admin_purge_post,
captcha_answer, captcha_answer,
comment, comment,
comment_actions,
comment_aggregates, comment_aggregates,
comment_like,
comment_reply, comment_reply,
comment_report, comment_report,
comment_saved,
community, community,
community_actions,
community_aggregates, community_aggregates,
community_block,
community_follower,
community_language, community_language,
community_moderator,
community_person_ban,
custom_emoji, custom_emoji,
custom_emoji_keyword, custom_emoji_keyword,
email_verification, email_verification,
@ -1065,7 +982,7 @@ diesel::allow_tables_to_appear_in_same_query!(
federation_queue_state, federation_queue_state,
image_details, image_details,
instance, instance,
instance_block, instance_actions,
language, language,
local_image, local_image,
local_site, local_site,
@ -1088,19 +1005,14 @@ diesel::allow_tables_to_appear_in_same_query!(
mod_transfer_community, mod_transfer_community,
password_reset_request, password_reset_request,
person, person,
person_actions,
person_aggregates, person_aggregates,
person_ban, person_ban,
person_block,
person_follower,
person_mention, person_mention,
person_post_aggregates,
post, post,
post_actions,
post_aggregates, post_aggregates,
post_hide,
post_like,
post_read,
post_report, post_report,
post_saved,
private_message, private_message,
private_message_report, private_message_report,
received_activity, received_activity,

View File

@ -2,9 +2,11 @@
use crate::newtypes::LtreeDef; use crate::newtypes::LtreeDef;
use crate::newtypes::{CommentId, DbUrl, LanguageId, PersonId, PostId}; use crate::newtypes::{CommentId, DbUrl, LanguageId, PersonId, PostId};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::{comment, comment_like, comment_saved}; use crate::schema::{comment, comment_actions};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
#[cfg(feature = "full")]
use diesel_ltree::Ltree; use diesel_ltree::Ltree;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
@ -93,24 +95,31 @@ pub struct CommentUpdateForm {
derive(Identifiable, Queryable, Selectable, Associations) derive(Identifiable, Queryable, Selectable, Associations)
)] )]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))]
#[cfg_attr(feature = "full", diesel(table_name = comment_like))] #[cfg_attr(feature = "full", diesel(table_name = comment_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct CommentLike { pub struct CommentLike {
pub person_id: PersonId, pub person_id: PersonId,
pub comment_id: CommentId, pub comment_id: CommentId,
#[cfg_attr(feature = "full", diesel(select_expression = comment_actions::post_id.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<comment_actions::post_id>))]
pub post_id: PostId, // TODO this is redundant pub post_id: PostId, // TODO this is redundant
#[cfg_attr(feature = "full", diesel(select_expression = comment_actions::like_score.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<comment_actions::like_score>))]
pub score: i16, pub score: i16,
#[cfg_attr(feature = "full", diesel(select_expression = comment_actions::liked.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<comment_actions::liked>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = comment_like))] #[cfg_attr(feature = "full", diesel(table_name = comment_actions))]
pub struct CommentLikeForm { pub struct CommentLikeForm {
pub person_id: PersonId, pub person_id: PersonId,
pub comment_id: CommentId, pub comment_id: CommentId,
pub post_id: PostId, // TODO this is redundant pub post_id: PostId, // TODO this is redundant
#[cfg_attr(feature = "full", diesel(column_name = like_score))]
pub score: i16, pub score: i16,
} }
@ -120,17 +129,19 @@ pub struct CommentLikeForm {
derive(Identifiable, Queryable, Selectable, Associations) derive(Identifiable, Queryable, Selectable, Associations)
)] )]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))]
#[cfg_attr(feature = "full", diesel(table_name = comment_saved))] #[cfg_attr(feature = "full", diesel(table_name = comment_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct CommentSaved { pub struct CommentSaved {
pub comment_id: CommentId, pub comment_id: CommentId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = comment_actions::saved.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<comment_actions::saved>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = comment_saved))] #[cfg_attr(feature = "full", diesel(table_name = comment_actions))]
pub struct CommentSavedForm { pub struct CommentSavedForm {
pub comment_id: CommentId, pub comment_id: CommentId,
pub person_id: PersonId, pub person_id: PersonId,

View File

@ -1,5 +1,5 @@
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::{community, community_follower, community_moderator, community_person_ban}; use crate::schema::{community, community_actions};
use crate::{ use crate::{
newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, newtypes::{CommunityId, DbUrl, InstanceId, PersonId},
sensitive::SensitiveString, sensitive::SensitiveString,
@ -7,6 +7,8 @@ use crate::{
CommunityVisibility, CommunityVisibility,
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
#[cfg(feature = "full")] #[cfg(feature = "full")]
@ -142,18 +144,20 @@ pub struct CommunityUpdateForm {
feature = "full", feature = "full",
diesel(belongs_to(crate::source::community::Community)) diesel(belongs_to(crate::source::community::Community))
)] )]
#[cfg_attr(feature = "full", diesel(table_name = community_moderator))] #[cfg_attr(feature = "full", diesel(table_name = community_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct CommunityModerator { pub struct CommunityModerator {
pub community_id: CommunityId, pub community_id: CommunityId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::became_moderator.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::became_moderator>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = community_moderator))] #[cfg_attr(feature = "full", diesel(table_name = community_actions))]
pub struct CommunityModeratorForm { pub struct CommunityModeratorForm {
pub community_id: CommunityId, pub community_id: CommunityId,
pub person_id: PersonId, pub person_id: PersonId,
@ -168,22 +172,26 @@ pub struct CommunityModeratorForm {
feature = "full", feature = "full",
diesel(belongs_to(crate::source::community::Community)) diesel(belongs_to(crate::source::community::Community))
)] )]
#[cfg_attr(feature = "full", diesel(table_name = community_person_ban))] #[cfg_attr(feature = "full", diesel(table_name = community_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct CommunityPersonBan { pub struct CommunityPersonBan {
pub community_id: CommunityId, pub community_id: CommunityId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::received_ban.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::received_ban>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
#[cfg_attr(feature = "full", diesel(column_name = ban_expires))]
pub expires: Option<DateTime<Utc>>, pub expires: Option<DateTime<Utc>>,
} }
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = community_person_ban))] #[cfg_attr(feature = "full", diesel(table_name = community_actions))]
pub struct CommunityPersonBanForm { pub struct CommunityPersonBanForm {
pub community_id: CommunityId, pub community_id: CommunityId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(column_name = ban_expires))]
pub expires: Option<Option<DateTime<Utc>>>, pub expires: Option<Option<DateTime<Utc>>>,
} }
@ -196,21 +204,26 @@ pub struct CommunityPersonBanForm {
feature = "full", feature = "full",
diesel(belongs_to(crate::source::community::Community)) diesel(belongs_to(crate::source::community::Community))
)] )]
#[cfg_attr(feature = "full", diesel(table_name = community_follower))] #[cfg_attr(feature = "full", diesel(table_name = community_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct CommunityFollower { pub struct CommunityFollower {
pub community_id: CommunityId, pub community_id: CommunityId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::followed.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::followed>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::follow_pending.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::follow_pending>))]
pub pending: bool, pub pending: bool,
} }
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = community_follower))] #[cfg_attr(feature = "full", diesel(table_name = community_actions))]
pub struct CommunityFollowerForm { pub struct CommunityFollowerForm {
pub community_id: CommunityId, pub community_id: CommunityId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(column_name = follow_pending))]
pub pending: bool, pub pending: bool,
} }

View File

@ -1,7 +1,9 @@
use crate::newtypes::{CommunityId, PersonId}; use crate::newtypes::{CommunityId, PersonId};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::community_block; use crate::schema::community_actions;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
@ -13,17 +15,19 @@ use serde::{Deserialize, Serialize};
feature = "full", feature = "full",
diesel(belongs_to(crate::source::community::Community)) diesel(belongs_to(crate::source::community::Community))
)] )]
#[cfg_attr(feature = "full", diesel(table_name = community_block))] #[cfg_attr(feature = "full", diesel(table_name = community_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct CommunityBlock { pub struct CommunityBlock {
pub person_id: PersonId, pub person_id: PersonId,
pub community_id: CommunityId, pub community_id: CommunityId,
#[cfg_attr(feature = "full", diesel(select_expression = community_actions::blocked.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<community_actions::blocked>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = community_block))] #[cfg_attr(feature = "full", diesel(table_name = community_actions))]
pub struct CommunityBlockForm { pub struct CommunityBlockForm {
pub person_id: PersonId, pub person_id: PersonId,
pub community_id: CommunityId, pub community_id: CommunityId,

View File

@ -1,7 +1,9 @@
use crate::newtypes::{InstanceId, PersonId}; use crate::newtypes::{InstanceId, PersonId};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::instance_block; use crate::schema::instance_actions;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
@ -13,17 +15,19 @@ use serde::{Deserialize, Serialize};
feature = "full", feature = "full",
diesel(belongs_to(crate::source::instance::Instance)) diesel(belongs_to(crate::source::instance::Instance))
)] )]
#[cfg_attr(feature = "full", diesel(table_name = instance_block))] #[cfg_attr(feature = "full", diesel(table_name = instance_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, instance_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, instance_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct InstanceBlock { pub struct InstanceBlock {
pub person_id: PersonId, pub person_id: PersonId,
pub instance_id: InstanceId, pub instance_id: InstanceId,
#[cfg_attr(feature = "full", diesel(select_expression = instance_actions::blocked.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<instance_actions::blocked>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = instance_block))] #[cfg_attr(feature = "full", diesel(table_name = instance_actions))]
pub struct InstanceBlockForm { pub struct InstanceBlockForm {
pub person_id: PersonId, pub person_id: PersonId,
pub instance_id: InstanceId, pub instance_id: InstanceId,

View File

@ -1,11 +1,13 @@
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::{person, person_follower}; use crate::schema::{person, person_actions};
use crate::{ use crate::{
newtypes::{DbUrl, InstanceId, PersonId}, newtypes::{DbUrl, InstanceId, PersonId},
sensitive::SensitiveString, sensitive::SensitiveString,
source::placeholder_apub_url, source::placeholder_apub_url,
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
#[cfg(feature = "full")] #[cfg(feature = "full")]
@ -131,21 +133,30 @@ pub struct PersonUpdateForm {
derive(Identifiable, Queryable, Selectable, Associations) derive(Identifiable, Queryable, Selectable, Associations)
)] )]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
#[cfg_attr(feature = "full", diesel(table_name = person_follower))] #[cfg_attr(feature = "full", diesel(table_name = person_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(follower_id, person_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, target_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct PersonFollower { pub struct PersonFollower {
#[cfg_attr(feature = "full", diesel(column_name = target_id))]
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(column_name = person_id))]
pub follower_id: PersonId, pub follower_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = person_actions::followed.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<person_actions::followed>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
#[cfg_attr(feature = "full", diesel(select_expression = person_actions::follow_pending.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<person_actions::follow_pending>))]
pub pending: bool, pub pending: bool,
} }
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = person_follower))] #[cfg_attr(feature = "full", diesel(table_name = person_actions))]
pub struct PersonFollowerForm { pub struct PersonFollowerForm {
#[cfg_attr(feature = "full", diesel(column_name = target_id))]
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(column_name = person_id))]
pub follower_id: PersonId, pub follower_id: PersonId,
#[cfg_attr(feature = "full", diesel(column_name = follow_pending))]
pub pending: bool, pub pending: bool,
} }

View File

@ -1,7 +1,9 @@
use crate::newtypes::PersonId; use crate::newtypes::PersonId;
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::person_block; use crate::schema::person_actions;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
@ -10,17 +12,19 @@ use serde::{Deserialize, Serialize};
derive(Queryable, Selectable, Associations, Identifiable) derive(Queryable, Selectable, Associations, Identifiable)
)] )]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
#[cfg_attr(feature = "full", diesel(table_name = person_block))] #[cfg_attr(feature = "full", diesel(table_name = person_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(person_id, target_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, target_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct PersonBlock { pub struct PersonBlock {
pub person_id: PersonId, pub person_id: PersonId,
pub target_id: PersonId, pub target_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = person_actions::blocked.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<person_actions::blocked>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = person_block))] #[cfg_attr(feature = "full", diesel(table_name = person_actions))]
pub struct PersonBlockForm { pub struct PersonBlockForm {
pub person_id: PersonId, pub person_id: PersonId,
pub target_id: PersonId, pub target_id: PersonId,

View File

@ -1,7 +1,9 @@
use crate::newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId}; use crate::newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::{post, post_hide, post_like, post_read, post_saved}; use crate::schema::{post, post_actions};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
#[cfg(feature = "full")]
use diesel::{dsl, expression_methods::NullableExpressionMethods};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
#[cfg(feature = "full")] #[cfg(feature = "full")]
@ -124,22 +126,27 @@ pub struct PostUpdateForm {
derive(Identifiable, Queryable, Selectable, Associations) derive(Identifiable, Queryable, Selectable, Associations)
)] )]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
#[cfg_attr(feature = "full", diesel(table_name = post_like))] #[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(primary_key(person_id, post_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct PostLike { pub struct PostLike {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::like_score.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::like_score>))]
pub score: i16, pub score: i16,
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::liked.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::liked>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = post_like))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))]
pub struct PostLikeForm { pub struct PostLikeForm {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(column_name = like_score))]
pub score: i16, pub score: i16,
} }
@ -149,17 +156,19 @@ pub struct PostLikeForm {
derive(Identifiable, Queryable, Selectable, Associations) derive(Identifiable, Queryable, Selectable, Associations)
)] )]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
#[cfg_attr(feature = "full", diesel(table_name = post_saved))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct PostSaved { pub struct PostSaved {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::saved.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::saved>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = post_saved))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))]
pub struct PostSavedForm { pub struct PostSavedForm {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,
@ -171,17 +180,19 @@ pub struct PostSavedForm {
derive(Identifiable, Queryable, Selectable, Associations) derive(Identifiable, Queryable, Selectable, Associations)
)] )]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
#[cfg_attr(feature = "full", diesel(table_name = post_read))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct PostRead { pub struct PostRead {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::read.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::read>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = post_read))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))]
pub(crate) struct PostReadForm { pub(crate) struct PostReadForm {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,
@ -193,17 +204,19 @@ pub(crate) struct PostReadForm {
derive(Identifiable, Queryable, Selectable, Associations) derive(Identifiable, Queryable, Selectable, Associations)
)] )]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
#[cfg_attr(feature = "full", diesel(table_name = post_hide))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))]
#[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct PostHide { pub struct PostHide {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", diesel(select_expression = post_actions::hidden.assume_not_null()))]
#[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull<post_actions::hidden>))]
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
} }
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = post_hide))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))]
pub(crate) struct PostHideForm { pub(crate) struct PostHideForm {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
diesel::OptionalExtension, diesel::OptionalExtension,
newtypes::{CommunityId, DbUrl, PersonId}, newtypes::{CommunityId, DbUrl, PersonId},
utils::{get_conn, DbPool}, utils::{get_conn, uplete, DbPool},
}; };
use diesel::{ use diesel::{
associations::HasTable, associations::HasTable,
@ -77,7 +77,7 @@ pub trait Followable {
) -> Result<Self, Error> ) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
async fn unfollow(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error> async fn unfollow(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
where where
Self: Sized; Self: Sized;
} }
@ -88,7 +88,7 @@ pub trait Joinable {
async fn join(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error> async fn join(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error> async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
where where
Self: Sized; Self: Sized;
} }
@ -104,7 +104,7 @@ pub trait Likeable {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
person_id: PersonId, person_id: PersonId,
item_id: Self::IdType, item_id: Self::IdType,
) -> Result<usize, Error> ) -> Result<uplete::Count, Error>
where where
Self: Sized; Self: Sized;
} }
@ -115,7 +115,7 @@ pub trait Bannable {
async fn ban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error> async fn ban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error> async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
where where
Self: Sized; Self: Sized;
} }
@ -126,7 +126,7 @@ pub trait Saveable {
async fn save(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error> async fn save(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error> async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
where where
Self: Sized; Self: Sized;
} }
@ -137,7 +137,7 @@ pub trait Blockable {
async fn block(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error> async fn block(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<usize, Error> async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<uplete::Count, Error>
where where
Self: Sized; Self: Sized;
} }

View File

@ -1,5 +1,6 @@
pub mod uplete;
use crate::{ use crate::{
diesel::ExpressionMethods,
newtypes::{DbUrl, PersonId}, newtypes::{DbUrl, PersonId},
schema::community, schema::community,
CommentSortType, CommentSortType,
@ -10,18 +11,27 @@ use chrono::{DateTime, TimeDelta, Utc};
use deadpool::Runtime; use deadpool::Runtime;
use diesel::{ use diesel::{
dsl, dsl,
expression::AsExpression,
helper_types::AsExprOf, helper_types::AsExprOf,
pg::Pg, pg::Pg,
query_builder::{Query, QueryFragment}, query_builder::{Query, QueryFragment},
query_dsl::methods::LimitDsl, query_dsl::methods::{FilterDsl, FindDsl, LimitDsl},
query_source::{Alias, AliasSource, AliasedField},
result::{ result::{
ConnectionError, ConnectionError,
ConnectionResult, ConnectionResult,
Error::{self as DieselError, QueryBuilderError}, Error::{self as DieselError, QueryBuilderError},
}, },
sql_types::{self, Timestamptz}, sql_types::{self, SingleValue, Timestamptz},
Column,
Expression,
ExpressionMethods,
IntoSql, IntoSql,
JoinOnDsl,
NullableExpressionMethods,
OptionalExtension, OptionalExtension,
QuerySource,
Table,
}; };
use diesel_async::{ use diesel_async::{
pg::AsyncPgConnection, pg::AsyncPgConnection,
@ -33,6 +43,7 @@ use diesel_async::{
AsyncConnection, AsyncConnection,
RunQueryDsl, RunQueryDsl,
}; };
use diesel_bind_if_some::BindIfSome;
use futures_util::{future::BoxFuture, Future, FutureExt}; use futures_util::{future::BoxFuture, Future, FutureExt};
use i_love_jesus::CursorKey; use i_love_jesus::CursorKey;
use lemmy_utils::{ use lemmy_utils::{
@ -530,6 +541,117 @@ pub fn now() -> AsExprOf<diesel::dsl::now, diesel::sql_types::Timestamptz> {
diesel::dsl::now.into_sql::<Timestamptz>() diesel::dsl::now.into_sql::<Timestamptz>()
} }
/// Trait alias for a type that can be converted to an SQL tuple using `IntoSql::into_sql`
pub trait AsRecord: Expression + AsExpression<sql_types::Record<Self::SqlType>>
where
Self::SqlType: 'static,
{
}
impl<T: Expression + AsExpression<sql_types::Record<T::SqlType>>> AsRecord for T where
T::SqlType: 'static
{
}
/// Output of `IntoSql::into_sql` for a type that implements `AsRecord`
pub type AsRecordOutput<T> = dsl::AsExprOf<T, sql_types::Record<<T as Expression>::SqlType>>;
/// Output of `t.on((l0, l1).into_sql().eq((r0, r1)))`
type OnTupleEq<T, L0, L1, R0, R1> = dsl::On<T, dsl::Eq<AsRecordOutput<(L0, L1)>, (R0, R1)>>;
/// Creates an `ON` clause for a table where a person ID and another column are used as the
/// primary key. Use with the `QueryDsl::left_join` method.
///
/// This example modifies a query to make columns in `community_actions` available:
///
/// ```
/// community::table
/// .left_join(actions(
/// community_actions::table,
/// my_person_id,
/// community::id,
/// ))
/// ```
pub fn actions<T, P, C, K0, K1>(
actions_table: T,
person_id: Option<P>,
target_id: C,
) -> OnTupleEq<T, dsl::Nullable<K0>, K1, BindIfSome<dsl::AsExprOf<P, sql_types::Integer>>, C>
where
T: Table<PrimaryKey = (K0, K1)> + Copy,
K0: Expression,
P: AsExpression<sql_types::Integer>,
(dsl::Nullable<K0>, K1): AsRecord,
(BindIfSome<dsl::AsExprOf<P, sql_types::Integer>>, C):
AsExpression<<AsRecordOutput<(dsl::Nullable<K0>, K1)> as Expression>::SqlType>,
{
let (k0, k1) = actions_table.primary_key();
actions_table.on((k0.nullable(), k1).into_sql().eq((
BindIfSome(person_id.map(diesel::IntoSql::into_sql)),
target_id,
)))
}
/// Like `actions` but `actions_table` is an alias and person id is not nullable
#[allow(clippy::type_complexity)]
pub fn actions_alias<T, P, C, K0, K1>(
actions_table: Alias<T>,
person_id: P,
target_id: C,
) -> OnTupleEq<Alias<T>, AliasedField<T, K0>, AliasedField<T, K1>, P, C>
where
Alias<T>: QuerySource + Copy,
T: AliasSource<Target: Table<PrimaryKey = (K0, K1)>> + Default,
K0: Column<Table = T::Target>,
K1: Column<Table = T::Target>,
(AliasedField<T, K0>, AliasedField<T, K1>): AsRecord,
(P, C): AsExpression<
<AsRecordOutput<(AliasedField<T, K0>, AliasedField<T, K1>)> as Expression>::SqlType,
>,
{
let (k0, k1) = T::default().target().primary_key();
actions_table.on(
(actions_table.field(k0), actions_table.field(k1))
.into_sql()
.eq((person_id, target_id)),
)
}
/// `action_query(table_name::action_name)` is the same as
/// `table_name::table.filter(table_name::action_name.is_not_null())`.
pub fn action_query<C>(column: C) -> dsl::Filter<C::Table, dsl::IsNotNull<C>>
where
C: Column<Table: Default + FilterDsl<dsl::IsNotNull<C>>, SqlType: SingleValue>,
{
action_query_with_fn(column, |t| t)
}
/// `find_action(table_name::action_name, key)` is the same as
/// `table_name::table.find(key).filter(table_name::action_name.is_not_null())`.
pub fn find_action<C, K>(
column: C,
key: K,
) -> dsl::Filter<dsl::Find<C::Table, K>, dsl::IsNotNull<C>>
where
C:
Column<Table: Default + FindDsl<K, Output: FilterDsl<dsl::IsNotNull<C>>>, SqlType: SingleValue>,
{
action_query_with_fn(column, |t| t.find(key))
}
/// `action_query_with_fn(table_name::action_name, f)` is the same as
/// `f(table_name::table).filter(table_name::action_name.is_not_null())`.
fn action_query_with_fn<C, Q>(
column: C,
f: impl FnOnce(C::Table) -> Q,
) -> dsl::Filter<Q, dsl::IsNotNull<C>>
where
C: Column<Table: Default, SqlType: SingleValue>,
Q: FilterDsl<dsl::IsNotNull<C>>,
{
f(C::Table::default()).filter(column.is_not_null())
}
pub type ResultFuture<'a, T> = BoxFuture<'a, Result<T, DieselError>>; pub type ResultFuture<'a, T> = BoxFuture<'a, Result<T, DieselError>>;
pub trait ReadFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, T> {} pub trait ReadFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, T> {}

View File

@ -0,0 +1,225 @@
use diesel::{
associations::HasTable,
dsl,
expression::{is_aggregate, ValidGrouping},
pg::Pg,
query_builder::{AsQuery, AstPass, Query, QueryFragment, QueryId},
query_dsl::methods::{FilterDsl, SelectDsl},
result::Error,
sql_types,
Column,
Expression,
Table,
};
use std::any::TypeId;
use tuplex::IntoArray;
/// Set columns (each specified with `UpleteBuilder::set_null`) to null in the rows found by
/// `query`, and delete rows that have no remaining non-null values outside of the primary key
pub fn new<Q>(query: Q) -> UpleteBuilder<dsl::Select<Q::Query, <Q::Table as Table>::PrimaryKey>>
where
Q: AsQuery + HasTable,
Q::Table: Default,
Q::Query: SelectDsl<<Q::Table as Table>::PrimaryKey>,
// For better error messages
UpleteBuilder<Q>: AsQuery,
{
UpleteBuilder {
query: query.as_query().select(Q::Table::default().primary_key()),
set_null_columns: Vec::new(),
}
}
pub struct UpleteBuilder<Q> {
query: Q,
set_null_columns: Vec<DynColumn>,
}
impl<Q: HasTable> UpleteBuilder<Q> {
pub fn set_null<C: Column<Table = Q::Table> + Into<DynColumn>>(mut self, column: C) -> Self {
self.set_null_columns.push(column.into());
self
}
}
impl<Q> AsQuery for UpleteBuilder<Q>
where
Q: HasTable,
Q::Table: Default + QueryFragment<Pg> + Send + 'static,
<Q::Table as Table>::PrimaryKey: IntoArray<DynColumn> + QueryFragment<Pg> + Send + 'static,
<Q::Table as Table>::AllColumns: IntoArray<DynColumn>,
<<Q::Table as Table>::PrimaryKey as IntoArray<DynColumn>>::Output: IntoIterator<Item = DynColumn>,
<<Q::Table as Table>::AllColumns as IntoArray<DynColumn>>::Output: IntoIterator<Item = DynColumn>,
Q: Clone + FilterDsl<AllNull> + FilterDsl<dsl::not<AllNull>>,
dsl::Filter<Q, AllNull>: QueryFragment<Pg> + Send + 'static,
dsl::Filter<Q, dsl::not<AllNull>>: QueryFragment<Pg> + Send + 'static,
{
type Query = UpleteQuery;
type SqlType = (sql_types::BigInt, sql_types::BigInt);
fn as_query(self) -> Self::Query {
let table = Q::Table::default;
let deletion_condition = AllNull(
Q::Table::all_columns()
.into_array()
.into_iter()
.filter(|c: &DynColumn| {
table()
.primary_key()
.into_array()
.into_iter()
.chain(self.set_null_columns.iter().cloned())
.all(|excluded_column| excluded_column.type_id != c.type_id)
})
.collect::<Vec<_>>(),
);
UpleteQuery {
// Updated rows and deleted rows must not overlap, so updating all rows and using the returned
// new rows to determine which ones to delete is not an option.
//
// https://www.postgresql.org/docs/16/queries-with.html#QUERIES-WITH-MODIFYING
//
// "Trying to update the same row twice in a single statement is not supported. Only one of
// the modifications takes place, but it is not easy (and sometimes not possible) to reliably
// predict which one. This also applies to deleting a row that was already updated in the same
// statement: only the update is performed."
update_subquery: Box::new(
self
.query
.clone()
.filter(dsl::not(deletion_condition.clone())),
),
delete_subquery: Box::new(self.query.filter(deletion_condition)),
table: Box::new(table()),
primary_key: Box::new(table().primary_key()),
set_null_columns: self.set_null_columns,
}
}
}
pub struct UpleteQuery {
update_subquery: Box<dyn QueryFragment<Pg> + Send + 'static>,
delete_subquery: Box<dyn QueryFragment<Pg> + Send + 'static>,
table: Box<dyn QueryFragment<Pg> + Send + 'static>,
primary_key: Box<dyn QueryFragment<Pg> + Send + 'static>,
set_null_columns: Vec<DynColumn>,
}
impl QueryId for UpleteQuery {
type QueryId = ();
const HAS_STATIC_QUERY_ID: bool = false;
}
impl Query for UpleteQuery {
type SqlType = (sql_types::BigInt, sql_types::BigInt);
}
impl QueryFragment<Pg> for UpleteQuery {
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> {
assert_ne!(self.set_null_columns.len(), 0, "`set_null` was not called");
// Declare `update_keys` and `delete_keys` CTEs, which select primary keys
for (prefix, subquery) in [
("WITH update_keys", &self.update_subquery),
(", delete_keys", &self.delete_subquery),
] {
out.push_sql(prefix);
out.push_sql(" AS (");
subquery.walk_ast(out.reborrow())?;
out.push_sql(" FOR UPDATE)");
}
// Update rows that are referenced in `update_keys`
out.push_sql(", update_result AS (UPDATE ");
self.table.walk_ast(out.reborrow())?;
let mut item_prefix = " SET ";
for column in &self.set_null_columns {
out.push_sql(item_prefix);
out.push_identifier(column.name)?;
out.push_sql(" = NULL");
item_prefix = ",";
}
out.push_sql(" WHERE (");
self.primary_key.walk_ast(out.reborrow())?;
out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)");
// Delete rows that are referenced in `delete_keys`
out.push_sql(", delete_result AS (DELETE FROM ");
self.table.walk_ast(out.reborrow())?;
out.push_sql(" WHERE (");
self.primary_key.walk_ast(out.reborrow())?;
out.push_sql(") = ANY (SELECT * FROM delete_keys) RETURNING 1)");
// Count updated rows and deleted rows (`RETURNING 1` makes this possible)
out.push_sql(" SELECT (SELECT count(*) FROM update_result)");
out.push_sql(", (SELECT count(*) FROM delete_result)");
Ok(())
}
}
#[derive(Clone)]
pub struct AllNull(Vec<DynColumn>);
impl Expression for AllNull {
type SqlType = sql_types::Bool;
}
impl ValidGrouping<()> for AllNull {
type IsAggregate = is_aggregate::No;
}
impl QueryFragment<Pg> for AllNull {
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> {
// Must produce a valid expression even if `self.0` is empty
out.push_sql("(TRUE");
for column in &self.0 {
out.push_sql(" AND (");
out.push_identifier(column.name)?;
out.push_sql(" IS NULL)");
}
out.push_sql(")");
Ok(())
}
}
#[derive(Clone)]
pub struct DynColumn {
type_id: TypeId,
name: &'static str,
}
impl<T: Column + 'static> From<T> for DynColumn {
fn from(_value: T) -> Self {
DynColumn {
type_id: TypeId::of::<T>(),
name: T::NAME,
}
}
}
#[derive(Queryable, PartialEq, Eq, Debug)]
pub struct Count {
pub updated: i64,
pub deleted: i64,
}
impl Count {
pub fn only_updated(n: i64) -> Self {
Count {
updated: n,
deleted: 0,
}
}
pub fn only_deleted(n: i64) -> Self {
Count {
updated: 0,
deleted: n,
}
}
}

View File

@ -11,24 +11,33 @@ use diesel::{
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases, aliases::{self, creator_community_actions},
newtypes::{CommentId, CommentReportId, CommunityId, PersonId}, newtypes::{CommentId, CommentReportId, CommunityId, PersonId},
schema::{ schema::{
comment, comment,
comment_actions,
comment_aggregates, comment_aggregates,
comment_like,
comment_report, comment_report,
comment_saved,
community, community,
community_follower, community_actions,
community_moderator,
community_person_ban,
local_user, local_user,
person, person,
person_block, person_actions,
post, post,
}, },
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, source::community::CommunityFollower,
utils::{
actions,
actions_alias,
functions::coalesce,
get_conn,
limit_and_offset,
DbConn,
DbPool,
ListFn,
Queries,
ReadFn,
},
}; };
fn queries<'a>() -> Queries< fn queries<'a>() -> Queries<
@ -45,40 +54,20 @@ fn queries<'a>() -> Queries<
.inner_join( .inner_join(
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
) )
.left_join( .left_join(actions(
comment_like::table.on( comment_actions::table,
comment::id Some(my_person_id),
.eq(comment_like::comment_id) comment_report::comment_id,
.and(comment_like::person_id.eq(my_person_id)), ))
),
)
.left_join( .left_join(
aliases::person2 aliases::person2
.on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), .on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())),
) )
.left_join( .left_join(actions_alias(
community_person_ban::table.on( creator_community_actions,
community::id comment::creator_id,
.eq(community_person_ban::community_id) post::community_id,
.and(community_person_ban::person_id.eq(comment::creator_id)) ))
.and(
community_person_ban::expires
.is_null()
.or(community_person_ban::expires.gt(now)),
),
),
)
.left_join(
aliases::community_moderator1.on(
community::id
.eq(aliases::community_moderator1.field(community_moderator::community_id))
.and(
aliases::community_moderator1
.field(community_moderator::person_id)
.eq(comment::creator_id),
),
),
)
.left_join( .left_join(
local_user::table.on( local_user::table.on(
comment::creator_id comment::creator_id
@ -86,27 +75,16 @@ fn queries<'a>() -> Queries<
.and(local_user::admin.eq(true)), .and(local_user::admin.eq(true)),
), ),
) )
.left_join( .left_join(actions(
person_block::table.on( person_actions::table,
comment::creator_id Some(my_person_id),
.eq(person_block::target_id) comment::creator_id,
.and(person_block::person_id.eq(my_person_id)), ))
), .left_join(actions(
) community_actions::table,
.left_join( Some(my_person_id),
community_follower::table.on( post::community_id,
post::community_id ))
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(my_person_id)),
),
)
.left_join(
comment_saved::table.on(
comment::id
.eq(comment_saved::comment_id)
.and(comment_saved::person_id.eq(my_person_id)),
),
)
.select(( .select((
comment_report::all_columns, comment_report::all_columns,
comment::all_columns, comment::all_columns,
@ -115,16 +93,28 @@ fn queries<'a>() -> Queries<
person::all_columns, person::all_columns,
aliases::person1.fields(person::all_columns), aliases::person1.fields(person::all_columns),
comment_aggregates::all_columns, comment_aggregates::all_columns,
community_person_ban::community_id.nullable().is_not_null(), coalesce(
aliases::community_moderator1 creator_community_actions
.field(community_moderator::community_id) .field(community_actions::received_ban)
.nullable()
.is_not_null()
.or(
creator_community_actions
.field(community_actions::ban_expires)
.nullable()
.gt(now),
),
false,
),
creator_community_actions
.field(community_actions::became_moderator)
.nullable() .nullable()
.is_not_null(), .is_not_null(),
local_user::admin.nullable().is_not_null(), local_user::admin.nullable().is_not_null(),
person_block::target_id.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(),
community_follower::pending.nullable(), CommunityFollower::select_subscribed_type(),
comment_saved::published.nullable().is_not_null(), comment_actions::saved.nullable().is_not_null(),
comment_like::score.nullable(), comment_actions::like_score.nullable(),
aliases::person2.fields(person::all_columns).nullable(), aliases::person2.fields(person::all_columns).nullable(),
)) ))
}; };
@ -166,19 +156,10 @@ fn queries<'a>() -> Queries<
// If its not an admin, get only the ones you mod // If its not an admin, get only the ones you mod
if !user.local_user.admin { if !user.local_user.admin {
query query = query.filter(community_actions::became_moderator.is_not_null());
.inner_join(
community_moderator::table.on(
community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(user.person.id)),
),
)
.load::<CommentReportView>(&mut conn)
.await
} else {
query.load::<CommentReportView>(&mut conn).await
} }
query.load::<CommentReportView>(&mut conn).await
}; };
Queries::new(read, list) Queries::new(read, list)
@ -221,10 +202,11 @@ impl CommentReportView {
if !admin { if !admin {
query query
.inner_join( .inner_join(
community_moderator::table.on( community_actions::table.on(
community_moderator::community_id community_actions::community_id
.eq(post::community_id) .eq(post::community_id)
.and(community_moderator::person_id.eq(my_person_id)), .and(community_actions::person_id.eq(my_person_id))
.and(community_actions::became_moderator.is_not_null()),
), ),
) )
.select(count(comment_report::id)) .select(count(comment_report::id))

View File

@ -3,11 +3,8 @@ use diesel::{
dsl::{exists, not}, dsl::{exists, not},
pg::Pg, pg::Pg,
result::Error, result::Error,
sql_types,
BoolExpressionMethods, BoolExpressionMethods,
BoxableExpression,
ExpressionMethods, ExpressionMethods,
IntoSql,
JoinOnDsl, JoinOnDsl,
NullableExpressionMethods, NullableExpressionMethods,
PgTextExpressionMethods, PgTextExpressionMethods,
@ -16,27 +13,26 @@ use diesel::{
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions}; use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions};
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases::creator_community_actions,
impls::local_user::LocalUserOptionHelper, impls::local_user::LocalUserOptionHelper,
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId}, newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
schema::{ schema::{
comment, comment,
comment_actions,
comment_aggregates, comment_aggregates,
comment_like,
comment_saved,
community, community,
community_block, community_actions,
community_follower, instance_actions,
community_moderator,
community_person_ban,
instance_block,
local_user, local_user,
local_user_language, local_user_language,
person, person,
person_block, person_actions,
post, post,
}, },
source::local_user::LocalUser, source::{community::CommunityFollower, local_user::LocalUser},
utils::{ utils::{
actions,
actions_alias,
fuzzy_search, fuzzy_search,
limit_and_offset, limit_and_offset,
visible_communities_only, visible_communities_only,
@ -54,64 +50,6 @@ fn queries<'a>() -> Queries<
impl ReadFn<'a, CommentView, (CommentId, Option<PersonId>)>, impl ReadFn<'a, CommentView, (CommentId, Option<PersonId>)>,
impl ListFn<'a, CommentView, CommentQuery<'a>>, impl ListFn<'a, CommentView, CommentQuery<'a>>,
> { > {
let is_creator_banned_from_community = exists(
community_person_ban::table.filter(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
);
let is_local_user_banned_from_community = |person_id| {
exists(
community_person_ban::table.filter(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(person_id)),
),
)
};
let is_community_followed = |person_id| {
community_follower::table
.filter(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id)),
)
.select(community_follower::pending.nullable())
.single_value()
};
let is_creator_blocked = |person_id| {
exists(
person_block::table.filter(
comment::creator_id
.eq(person_block::target_id)
.and(person_block::person_id.eq(person_id)),
),
)
};
let score = |person_id| {
comment_like::table
.filter(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(person_id)),
)
.select(comment_like::score.nullable())
.single_value()
};
let creator_is_moderator = exists(
community_moderator::table.filter(
community::id
.eq(community_moderator::community_id)
.and(community_moderator::person_id.eq(comment::creator_id)),
),
);
let creator_is_admin = exists( let creator_is_admin = exists(
local_user::table.filter( local_user::table.filter(
comment::creator_id comment::creator_id
@ -121,63 +59,56 @@ fn queries<'a>() -> Queries<
); );
let all_joins = move |query: comment::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| { let all_joins = move |query: comment::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| {
let is_local_user_banned_from_community_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>,
> = if let Some(person_id) = my_person_id {
Box::new(is_local_user_banned_from_community(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
let score_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::SmallInt>>,
> = if let Some(person_id) = my_person_id {
Box::new(score(person_id))
} else {
Box::new(None::<i16>.into_sql::<sql_types::Nullable<sql_types::SmallInt>>())
};
let subscribed_type_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::Bool>>,
> = if let Some(person_id) = my_person_id {
Box::new(is_community_followed(person_id))
} else {
Box::new(None::<bool>.into_sql::<sql_types::Nullable<sql_types::Bool>>())
};
let is_creator_blocked_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
if let Some(person_id) = my_person_id {
Box::new(is_creator_blocked(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
query query
.inner_join(person::table) .inner_join(person::table)
.inner_join(post::table) .inner_join(post::table)
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(comment_aggregates::table) .inner_join(comment_aggregates::table)
.left_join( .left_join(actions(
comment_saved::table.on( community_actions::table,
comment::id my_person_id,
.eq(comment_saved::comment_id) post::community_id,
.and(comment_saved::person_id.eq(my_person_id.unwrap_or(PersonId(-1)))), ))
), .left_join(actions(
) comment_actions::table,
my_person_id,
comment_aggregates::comment_id,
))
.left_join(actions(
person_actions::table,
my_person_id,
comment::creator_id,
))
.left_join(actions(
instance_actions::table,
my_person_id,
community::instance_id,
))
.left_join(actions_alias(
creator_community_actions,
comment::creator_id,
post::community_id,
))
.select(( .select((
comment::all_columns, comment::all_columns,
person::all_columns, person::all_columns,
post::all_columns, post::all_columns,
community::all_columns, community::all_columns,
comment_aggregates::all_columns, comment_aggregates::all_columns,
is_creator_banned_from_community, creator_community_actions
is_local_user_banned_from_community_selection, .field(community_actions::received_ban)
creator_is_moderator, .nullable()
.is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()
.is_not_null(),
creator_is_admin, creator_is_admin,
subscribed_type_selection, CommunityFollower::select_subscribed_type(),
comment_saved::person_id.nullable().is_not_null(), comment_actions::saved.nullable().is_not_null(),
is_creator_blocked_selection, person_actions::blocked.nullable().is_not_null(),
score_selection, comment_actions::like_score.nullable(),
)) ))
}; };
@ -190,7 +121,6 @@ fn queries<'a>() -> Queries<
let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move { let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move {
// The left join below will return None in this case // The left join below will return None in this case
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
let local_user_id_join = options let local_user_id_join = options
.local_user .local_user
.local_user_id() .local_user_id()
@ -223,13 +153,7 @@ fn queries<'a>() -> Queries<
} }
if let Some(listing_type) = options.listing_type { if let Some(listing_type) = options.listing_type {
let is_subscribed = exists( let is_subscribed = community_actions::followed.is_not_null();
community_follower::table.filter(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id_join)),
),
);
match listing_type { match listing_type {
ListingType::Subscribed => query = query.filter(is_subscribed), /* TODO could be this: and(community_follower::person_id.eq(person_id_join)), */ ListingType::Subscribed => query = query.filter(is_subscribed), /* TODO could be this: and(community_follower::person_id.eq(person_id_join)), */
@ -240,13 +164,7 @@ fn queries<'a>() -> Queries<
} }
ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)), ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)),
ListingType::ModeratorView => { ListingType::ModeratorView => {
query = query.filter(exists( query = query.filter(community_actions::became_moderator.is_not_null());
community_moderator::table.filter(
post::community_id
.eq(community_moderator::community_id)
.and(community_moderator::person_id.eq(person_id_join)),
),
));
} }
} }
} }
@ -254,16 +172,20 @@ fn queries<'a>() -> Queries<
// If its saved only, then filter, and order by the saved time, not the comment creation time. // If its saved only, then filter, and order by the saved time, not the comment creation time.
if options.saved_only.unwrap_or_default() { if options.saved_only.unwrap_or_default() {
query = query query = query
.filter(comment_saved::person_id.is_not_null()) .filter(comment_actions::saved.is_not_null())
.then_order_by(comment_saved::published.desc()); .then_order_by(comment_actions::saved.desc());
} }
if let Some(my_id) = options.local_user.person_id() { if let Some(my_id) = options.local_user.person_id() {
let not_creator_filter = comment::creator_id.ne(my_id); let not_creator_filter = comment::creator_id.ne(my_id);
if options.liked_only.unwrap_or_default() { if options.liked_only.unwrap_or_default() {
query = query.filter(not_creator_filter).filter(score(my_id).eq(1)); query = query
.filter(not_creator_filter)
.filter(comment_actions::like_score.eq(1));
} else if options.disliked_only.unwrap_or_default() { } else if options.disliked_only.unwrap_or_default() {
query = query.filter(not_creator_filter).filter(score(my_id).eq(-1)); query = query
.filter(not_creator_filter)
.filter(comment_actions::like_score.eq(-1));
} }
} }
@ -284,21 +206,10 @@ fn queries<'a>() -> Queries<
)); ));
// Don't show blocked communities or persons // Don't show blocked communities or persons
query = query.filter(not(exists( query = query
instance_block::table.filter( .filter(instance_actions::blocked.is_null())
community::instance_id .filter(community_actions::blocked.is_null())
.eq(instance_block::instance_id) .filter(person_actions::blocked.is_null());
.and(instance_block::person_id.eq(person_id_join)),
),
)));
query = query.filter(not(exists(
community_block::table.filter(
community::id
.eq(community_block::community_id)
.and(community_block::person_id.eq(person_id_join)),
),
)));
query = query.filter(not(is_creator_blocked(person_id_join)));
}; };
query = visible_communities_only(options.local_user.person_id(), query); query = visible_communities_only(options.local_user.person_id(), query);

View File

@ -10,26 +10,23 @@ use diesel::{
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases, aliases::{self, creator_community_actions},
newtypes::{CommunityId, PersonId, PostId, PostReportId}, newtypes::{CommunityId, PersonId, PostId, PostReportId},
schema::{ schema::{
community, community,
community_follower, community_actions,
community_moderator,
community_person_ban,
local_user, local_user,
person, person,
person_block, person_actions,
person_post_aggregates,
post, post,
post_actions,
post_aggregates, post_aggregates,
post_hide,
post_like,
post_read,
post_report, post_report,
post_saved,
}, },
source::community::CommunityFollower,
utils::{ utils::{
actions,
actions_alias,
functions::coalesce, functions::coalesce,
get_conn, get_conn,
limit_and_offset, limit_and_offset,
@ -51,25 +48,16 @@ fn queries<'a>() -> Queries<
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person::table.on(post_report::creator_id.eq(person::id))) .inner_join(person::table.on(post_report::creator_id.eq(person::id)))
.inner_join(aliases::person1.on(post::creator_id.eq(aliases::person1.field(person::id)))) .inner_join(aliases::person1.on(post::creator_id.eq(aliases::person1.field(person::id))))
.left_join( .left_join(actions_alias(
community_person_ban::table.on( creator_community_actions,
post::community_id post::creator_id,
.eq(community_person_ban::community_id) post::community_id,
.and(community_person_ban::person_id.eq(post::creator_id)), ))
), .left_join(actions(
) community_actions::table,
.left_join( Some(my_person_id),
aliases::community_moderator1.on( post::community_id,
aliases::community_moderator1 ))
.field(community_moderator::community_id)
.eq(post::community_id)
.and(
aliases::community_moderator1
.field(community_moderator::person_id)
.eq(my_person_id),
),
),
)
.left_join( .left_join(
local_user::table.on( local_user::table.on(
post::creator_id post::creator_id
@ -77,55 +65,12 @@ fn queries<'a>() -> Queries<
.and(local_user::admin.eq(true)), .and(local_user::admin.eq(true)),
), ),
) )
.left_join( .left_join(actions(post_actions::table, Some(my_person_id), post::id))
post_saved::table.on( .left_join(actions(
post::id person_actions::table,
.eq(post_saved::post_id) Some(my_person_id),
.and(post_saved::person_id.eq(my_person_id)), post::creator_id,
), ))
)
.left_join(
post_read::table.on(
post::id
.eq(post_read::post_id)
.and(post_read::person_id.eq(my_person_id)),
),
)
.left_join(
post_hide::table.on(
post::id
.eq(post_hide::post_id)
.and(post_hide::person_id.eq(my_person_id)),
),
)
.left_join(
person_block::table.on(
post::creator_id
.eq(person_block::target_id)
.and(person_block::person_id.eq(my_person_id)),
),
)
.left_join(
person_post_aggregates::table.on(
post::id
.eq(person_post_aggregates::post_id)
.and(person_post_aggregates::person_id.eq(my_person_id)),
),
)
.left_join(
community_follower::table.on(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(my_person_id)),
),
)
.left_join(
post_like::table.on(
post::id
.eq(post_like::post_id)
.and(post_like::person_id.eq(my_person_id)),
),
)
.inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)))
.left_join( .left_join(
aliases::person2 aliases::person2
@ -137,20 +82,23 @@ fn queries<'a>() -> Queries<
community::all_columns, community::all_columns,
person::all_columns, person::all_columns,
aliases::person1.fields(person::all_columns), aliases::person1.fields(person::all_columns),
community_person_ban::community_id.nullable().is_not_null(), creator_community_actions
aliases::community_moderator1 .field(community_actions::received_ban)
.field(community_moderator::community_id) .nullable()
.is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable() .nullable()
.is_not_null(), .is_not_null(),
local_user::admin.nullable().is_not_null(), local_user::admin.nullable().is_not_null(),
community_follower::pending.nullable(), CommunityFollower::select_subscribed_type(),
post_saved::post_id.nullable().is_not_null(), post_actions::saved.nullable().is_not_null(),
post_read::post_id.nullable().is_not_null(), post_actions::read.nullable().is_not_null(),
post_hide::post_id.nullable().is_not_null(), post_actions::hidden.nullable().is_not_null(),
person_block::target_id.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(),
post_like::score.nullable(), post_actions::like_score.nullable(),
coalesce( coalesce(
post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(), post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments, post_aggregates::comments,
), ),
post_aggregates::all_columns, post_aggregates::all_columns,
@ -194,19 +142,10 @@ fn queries<'a>() -> Queries<
// If its not an admin, get only the ones you mod // If its not an admin, get only the ones you mod
if !user.local_user.admin { if !user.local_user.admin {
query query = query.filter(community_actions::became_moderator.is_not_null());
.inner_join(
community_moderator::table.on(
community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(user.person.id)),
),
)
.load::<PostReportView>(&mut conn)
.await
} else {
query.load::<PostReportView>(&mut conn).await
} }
query.load::<PostReportView>(&mut conn).await
}; };
Queries::new(read, list) Queries::new(read, list)
@ -246,10 +185,11 @@ impl PostReportView {
if !admin { if !admin {
query query
.inner_join( .inner_join(
community_moderator::table.on( community_actions::table.on(
community_moderator::community_id community_actions::community_id
.eq(post::community_id) .eq(post::community_id)
.and(community_moderator::person_id.eq(my_person_id)), .and(community_actions::person_id.eq(my_person_id))
.and(community_actions::became_moderator.is_not_null()),
), ),
) )
.select(count(post_report::id)) .select(count(post_report::id))

View File

@ -5,11 +5,8 @@ use diesel::{
pg::Pg, pg::Pg,
query_builder::AsQuery, query_builder::AsQuery,
result::Error, result::Error,
sql_types,
BoolExpressionMethods, BoolExpressionMethods,
BoxableExpression,
ExpressionMethods, ExpressionMethods,
IntoSql,
JoinOnDsl, JoinOnDsl,
NullableExpressionMethods, NullableExpressionMethods,
OptionalExtension, OptionalExtension,
@ -20,30 +17,27 @@ use diesel_async::RunQueryDsl;
use i_love_jesus::PaginatedQueryBuilder; use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::{post_aggregates_keys as key, PostAggregates}, aggregates::structs::{post_aggregates_keys as key, PostAggregates},
aliases::creator_community_actions,
impls::local_user::LocalUserOptionHelper, impls::local_user::LocalUserOptionHelper,
newtypes::{CommunityId, LocalUserId, PersonId, PostId}, newtypes::{CommunityId, LocalUserId, PersonId, PostId},
schema::{ schema::{
community, community,
community_block, community_actions,
community_follower,
community_moderator,
community_person_ban,
image_details, image_details,
instance_block, instance_actions,
local_user, local_user,
local_user_language, local_user_language,
person, person,
person_block, person_actions,
person_post_aggregates,
post, post,
post_actions,
post_aggregates, post_aggregates,
post_hide,
post_like,
post_read,
post_saved,
}, },
source::{local_user::LocalUser, site::Site}, source::{community::CommunityFollower, local_user::LocalUser, site::Site},
utils::{ utils::{
action_query,
actions,
actions_alias,
functions::coalesce, functions::coalesce,
fuzzy_search, fuzzy_search,
get_conn, get_conn,
@ -67,32 +61,6 @@ fn queries<'a>() -> Queries<
impl ReadFn<'a, PostView, (PostId, Option<PersonId>, bool)>, impl ReadFn<'a, PostView, (PostId, Option<PersonId>, bool)>,
impl ListFn<'a, PostView, (PostQuery<'a>, &'a Site)>, impl ListFn<'a, PostView, (PostQuery<'a>, &'a Site)>,
> { > {
let is_creator_banned_from_community = exists(
community_person_ban::table.filter(
post_aggregates::community_id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(post_aggregates::creator_id)),
),
);
let is_local_user_banned_from_community = |person_id| {
exists(
community_person_ban::table.filter(
post_aggregates::community_id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(person_id)),
),
)
};
let creator_is_moderator = exists(
community_moderator::table.filter(
post_aggregates::community_id
.eq(community_moderator::community_id)
.and(community_moderator::person_id.eq(post_aggregates::creator_id)),
),
);
let creator_is_admin = exists( let creator_is_admin = exists(
local_user::table.filter( local_user::table.filter(
post_aggregates::creator_id post_aggregates::creator_id
@ -101,150 +69,62 @@ fn queries<'a>() -> Queries<
), ),
); );
let is_read = |person_id| {
exists(
post_read::table.filter(
post_aggregates::post_id
.eq(post_read::post_id)
.and(post_read::person_id.eq(person_id)),
),
)
};
let is_hidden = |person_id| {
exists(
post_hide::table.filter(
post_aggregates::post_id
.eq(post_hide::post_id)
.and(post_hide::person_id.eq(person_id)),
),
)
};
let is_creator_blocked = |person_id| {
exists(
person_block::table.filter(
post_aggregates::creator_id
.eq(person_block::target_id)
.and(person_block::person_id.eq(person_id)),
),
)
};
let score = |person_id| {
post_like::table
.filter(
post_aggregates::post_id
.eq(post_like::post_id)
.and(post_like::person_id.eq(person_id)),
)
.select(post_like::score.nullable())
.single_value()
};
let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>, let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>,
my_person_id: Option<PersonId>| { my_person_id: Option<PersonId>| {
let is_local_user_banned_from_community_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>,
> = if let Some(person_id) = my_person_id {
Box::new(is_local_user_banned_from_community(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
let is_read_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
if let Some(person_id) = my_person_id {
Box::new(is_read(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
let is_hidden_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
if let Some(person_id) = my_person_id {
Box::new(is_hidden(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
let is_creator_blocked_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
if let Some(person_id) = my_person_id {
Box::new(is_creator_blocked(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
let subscribed_type_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::Bool>>,
> = if let Some(person_id) = my_person_id {
Box::new(
community_follower::table
.filter(
post_aggregates::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id)),
)
.select(community_follower::pending.nullable())
.single_value(),
)
} else {
Box::new(None::<bool>.into_sql::<sql_types::Nullable<sql_types::Bool>>())
};
let score_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::SmallInt>>,
> = if let Some(person_id) = my_person_id {
Box::new(score(person_id))
} else {
Box::new(None::<i16>.into_sql::<sql_types::Nullable<sql_types::SmallInt>>())
};
let read_comments: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::BigInt>>,
> = if let Some(person_id) = my_person_id {
Box::new(
person_post_aggregates::table
.filter(
post_aggregates::post_id
.eq(person_post_aggregates::post_id)
.and(person_post_aggregates::person_id.eq(person_id)),
)
.select(person_post_aggregates::read_comments.nullable())
.single_value(),
)
} else {
Box::new(None::<i64>.into_sql::<sql_types::Nullable<sql_types::BigInt>>())
};
query query
.inner_join(person::table) .inner_join(person::table)
.inner_join(community::table) .inner_join(community::table)
.inner_join(post::table) .inner_join(post::table)
.left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()))) .left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())))
.left_join( .left_join(actions(
post_saved::table.on( community_actions::table,
post_aggregates::post_id my_person_id,
.eq(post_saved::post_id) post_aggregates::community_id,
.and(post_saved::person_id.eq(my_person_id.unwrap_or(PersonId(-1)))), ))
), .left_join(actions(
) person_actions::table,
my_person_id,
post_aggregates::creator_id,
))
.left_join(actions(
post_actions::table,
my_person_id,
post_aggregates::post_id,
))
.left_join(actions(
instance_actions::table,
my_person_id,
post_aggregates::instance_id,
))
.left_join(actions_alias(
creator_community_actions,
post_aggregates::creator_id,
post_aggregates::community_id,
))
.select(( .select((
post::all_columns, post::all_columns,
person::all_columns, person::all_columns,
community::all_columns, community::all_columns,
image_details::all_columns.nullable(), image_details::all_columns.nullable(),
is_creator_banned_from_community, creator_community_actions
is_local_user_banned_from_community_selection, .field(community_actions::received_ban)
creator_is_moderator, .nullable()
.is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()
.is_not_null(),
creator_is_admin, creator_is_admin,
post_aggregates::all_columns, post_aggregates::all_columns,
subscribed_type_selection, CommunityFollower::select_subscribed_type(),
post_saved::person_id.nullable().is_not_null(), post_actions::saved.nullable().is_not_null(),
is_read_selection, post_actions::read.nullable().is_not_null(),
is_hidden_selection, post_actions::hidden.nullable().is_not_null(),
is_creator_blocked_selection, person_actions::blocked.nullable().is_not_null(),
score_selection, post_actions::like_score.nullable(),
coalesce( coalesce(
post_aggregates::comments.nullable() - read_comments, post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
post_aggregates::comments, post_aggregates::comments,
), ),
)) ))
@ -299,7 +179,6 @@ fn queries<'a>() -> Queries<
let list = move |mut conn: DbConn<'a>, (options, site): (PostQuery<'a>, &'a Site)| async move { let list = move |mut conn: DbConn<'a>, (options, site): (PostQuery<'a>, &'a Site)| async move {
// The left join below will return None in this case // The left join below will return None in this case
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
let local_user_id_join = options let local_user_id_join = options
.local_user .local_user
.local_user_id() .local_user_id()
@ -335,42 +214,17 @@ fn queries<'a>() -> Queries<
} }
if let Some(listing_type) = options.listing_type { if let Some(listing_type) = options.listing_type {
if let Some(person_id) = options.local_user.person_id() { let is_subscribed = community_actions::followed.is_not_null();
let is_subscribed = exists( match listing_type {
community_follower::table.filter( ListingType::Subscribed => query = query.filter(is_subscribed),
post_aggregates::community_id ListingType::Local => {
.eq(community_follower::community_id) query = query
.and(community_follower::person_id.eq(person_id)), .filter(community::local.eq(true))
), .filter(community::hidden.eq(false).or(is_subscribed));
);
match listing_type {
ListingType::Subscribed => query = query.filter(is_subscribed),
ListingType::Local => {
query = query
.filter(community::local.eq(true))
.filter(community::hidden.eq(false).or(is_subscribed));
}
ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)),
ListingType::ModeratorView => {
query = query.filter(exists(
community_moderator::table.filter(
post::community_id
.eq(community_moderator::community_id)
.and(community_moderator::person_id.eq(person_id)),
),
));
}
} }
} ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)),
// If your person_id is missing, only show local ListingType::ModeratorView => {
else { query = query.filter(community_actions::became_moderator.is_not_null());
match listing_type {
ListingType::Local => {
query = query
.filter(community::local.eq(true))
.filter(community::hidden.eq(false));
}
_ => query = query.filter(community::hidden.eq(false)),
} }
} }
} else { } else {
@ -405,8 +259,8 @@ fn queries<'a>() -> Queries<
// If its saved only, then filter, and order by the saved time, not the comment creation time. // If its saved only, then filter, and order by the saved time, not the comment creation time.
if options.saved_only.unwrap_or_default() { if options.saved_only.unwrap_or_default() {
query = query query = query
.filter(post_saved::person_id.is_not_null()) .filter(post_actions::saved.is_not_null())
.then_order_by(post_saved::published.desc()); .then_order_by(post_actions::saved.desc());
} }
// Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read // Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read
// setting wont be able to see saved posts. // setting wont be able to see saved posts.
@ -416,59 +270,48 @@ fn queries<'a>() -> Queries<
{ {
// Do not hide read posts when it is a user profile view // Do not hide read posts when it is a user profile view
// Or, only hide read posts on non-profile views // Or, only hide read posts on non-profile views
if let (None, Some(person_id)) = (options.creator_id, options.local_user.person_id()) { if options.creator_id.is_none() {
query = query.filter(not(is_read(person_id))); query = query.filter(post_actions::read.is_null());
} }
} }
if !options.show_hidden.unwrap_or_default() { // If a creator id isn't given (IE its on home or community pages), hide the hidden posts
// If a creator id isn't given (IE its on home or community pages), hide the hidden posts if !options.show_hidden.unwrap_or_default() && options.creator_id.is_none() {
if let (None, Some(person_id)) = (options.creator_id, options.local_user.person_id()) { query = query.filter(post_actions::hidden.is_null());
query = query.filter(not(is_hidden(person_id)));
}
} }
if let Some(my_id) = options.local_user.person_id() { if let Some(my_id) = options.local_user.person_id() {
let not_creator_filter = post_aggregates::creator_id.ne(my_id); let not_creator_filter = post_aggregates::creator_id.ne(my_id);
if options.liked_only.unwrap_or_default() { if options.liked_only.unwrap_or_default() {
query = query.filter(not_creator_filter).filter(score(my_id).eq(1)); query = query
.filter(not_creator_filter)
.filter(post_actions::like_score.eq(1));
} else if options.disliked_only.unwrap_or_default() { } else if options.disliked_only.unwrap_or_default() {
query = query.filter(not_creator_filter).filter(score(my_id).eq(-1)); query = query
.filter(not_creator_filter)
.filter(post_actions::like_score.eq(-1));
} }
}; };
query = visible_communities_only(options.local_user.person_id(), query); query = visible_communities_only(options.local_user.person_id(), query);
// Dont filter blocks or missing languages for moderator view type // Dont filter blocks or missing languages for moderator view type
if let (Some(person_id), false) = ( if options.listing_type.unwrap_or_default() != ListingType::ModeratorView {
options.local_user.person_id(), // Filter out the rows with missing languages if user is logged in
options.listing_type.unwrap_or_default() == ListingType::ModeratorView, if options.local_user.is_some() {
) { query = query.filter(exists(
// Filter out the rows with missing languages local_user_language::table.filter(
query = query.filter(exists( post::language_id
local_user_language::table.filter( .eq(local_user_language::language_id)
post::language_id .and(local_user_language::local_user_id.eq(local_user_id_join)),
.eq(local_user_language::language_id) ),
.and(local_user_language::local_user_id.eq(local_user_id_join)), ));
), }
));
// Don't show blocked instances, communities or persons // Don't show blocked instances, communities or persons
query = query.filter(not(exists( query = query.filter(community_actions::blocked.is_null());
community_block::table.filter( query = query.filter(instance_actions::blocked.is_null());
post_aggregates::community_id query = query.filter(person_actions::blocked.is_null());
.eq(community_block::community_id)
.and(community_block::person_id.eq(person_id_join)),
),
)));
query = query.filter(not(exists(
instance_block::table.filter(
post_aggregates::instance_id
.eq(instance_block::instance_id)
.and(instance_block::person_id.eq(person_id_join)),
),
)));
query = query.filter(not(is_creator_blocked(person_id)));
} }
let (limit, offset) = limit_and_offset(options.page, options.limit)?; let (limit, offset) = limit_and_offset(options.page, options.limit)?;
@ -636,13 +479,10 @@ impl<'a> PostQuery<'a> {
// covers the "worst case" of the whole page consisting of posts from one community // 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 // but using the largest community decreases the pagination-frame so make the real query more
// efficient. // efficient.
use lemmy_db_schema::schema::{ use lemmy_db_schema::schema::community_aggregates::dsl::{
community_aggregates::dsl::{community_aggregates, community_id, users_active_month}, community_aggregates,
community_follower::dsl::{ community_id,
community_follower, users_active_month,
community_id as follower_community_id,
person_id,
},
}; };
let (limit, offset) = limit_and_offset(self.page, self.limit)?; let (limit, offset) = limit_and_offset(self.page, self.limit)?;
if offset != 0 && self.page_after.is_some() { if offset != 0 && self.page_after.is_some() {
@ -653,9 +493,9 @@ impl<'a> PostQuery<'a> {
let self_person_id = self.local_user.expect("part of the above if").person_id; let self_person_id = self.local_user.expect("part of the above if").person_id;
let largest_subscribed = { let largest_subscribed = {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
community_follower action_query(community_actions::followed)
.filter(person_id.eq(self_person_id)) .filter(community_actions::person_id.eq(self_person_id))
.inner_join(community_aggregates.on(community_id.eq(follower_community_id))) .inner_join(community_aggregates.on(community_id.eq(community_actions::community_id)))
.order_by(users_active_month.desc()) .order_by(users_active_month.desc())
.select(community_id) .select(community_id)
.limit(1) .limit(1)
@ -756,7 +596,7 @@ mod tests {
site::Site, site::Site,
}, },
traits::{Bannable, Blockable, Crud, Joinable, Likeable}, traits::{Bannable, Blockable, Crud, Joinable, Likeable},
utils::{build_db_pool, build_db_pool_for_tests, DbPool, RANK_DEFAULT}, utils::{build_db_pool, build_db_pool_for_tests, uplete, DbPool, RANK_DEFAULT},
CommunityVisibility, CommunityVisibility,
SortType, SortType,
SubscribedType, SubscribedType,
@ -1096,7 +936,7 @@ mod tests {
let like_removed = let like_removed =
PostLike::remove(pool, data.local_user_view.person.id, data.inserted_post.id).await?; PostLike::remove(pool, data.local_user_view.person.id, data.inserted_post.id).await?;
assert_eq!(1, like_removed); assert_eq!(uplete::Count::only_deleted(1), like_removed);
cleanup(data, pool).await cleanup(data, pool).await
} }

View File

@ -12,8 +12,8 @@ use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases, aliases,
newtypes::{PersonId, PrivateMessageId}, newtypes::{PersonId, PrivateMessageId},
schema::{instance_block, person, person_block, private_message}, schema::{instance_actions, person, person_actions, private_message},
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, utils::{actions, get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
}; };
use tracing::debug; use tracing::debug;
@ -27,20 +27,16 @@ fn queries<'a>() -> Queries<
.inner_join( .inner_join(
aliases::person1.on(private_message::recipient_id.eq(aliases::person1.field(person::id))), aliases::person1.on(private_message::recipient_id.eq(aliases::person1.field(person::id))),
) )
.left_join( .left_join(actions(
person_block::table.on( person_actions::table,
private_message::creator_id Some(aliases::person1.field(person::id)),
.eq(person_block::target_id) private_message::creator_id,
.and(person_block::person_id.eq(aliases::person1.field(person::id))), ))
), .left_join(actions(
) instance_actions::table,
.left_join( Some(aliases::person1.field(person::id)),
instance_block::table.on( person::instance_id,
person::instance_id ))
.eq(instance_block::instance_id)
.and(instance_block::person_id.eq(aliases::person1.field(person::id))),
),
)
}; };
let selection = ( let selection = (
@ -62,9 +58,9 @@ fn queries<'a>() -> Queries<
let mut query = all_joins(private_message::table.into_boxed()) let mut query = all_joins(private_message::table.into_boxed())
.select(selection) .select(selection)
// Dont show replies from blocked users // Dont show replies from blocked users
.filter(person_block::person_id.is_null()) .filter(person_actions::blocked.is_null())
// Dont show replies from blocked instances // Dont show replies from blocked instances
.filter(instance_block::person_id.is_null()); .filter(instance_actions::blocked.is_null());
// If its unread, I only want the ones to me // If its unread, I only want the ones to me
if options.unread_only { if options.unread_only {
@ -127,24 +123,20 @@ impl PrivateMessageView {
private_message::table private_message::table
// Necessary to get the senders instance_id // Necessary to get the senders instance_id
.inner_join(person::table.on(private_message::creator_id.eq(person::id))) .inner_join(person::table.on(private_message::creator_id.eq(person::id)))
.left_join( .left_join(actions(
person_block::table.on( person_actions::table,
private_message::creator_id Some(my_person_id),
.eq(person_block::target_id) private_message::creator_id,
.and(person_block::person_id.eq(my_person_id)), ))
), .left_join(actions(
) instance_actions::table,
.left_join( Some(my_person_id),
instance_block::table.on( person::instance_id,
person::instance_id ))
.eq(instance_block::instance_id)
.and(instance_block::person_id.eq(my_person_id)),
),
)
// Dont count replies from blocked users // Dont count replies from blocked users
.filter(person_block::person_id.is_null()) .filter(person_actions::blocked.is_null())
// Dont count replies from blocked instances // Dont count replies from blocked instances
.filter(instance_block::person_id.is_null()) .filter(instance_actions::blocked.is_null())
.filter(private_message::read.eq(false)) .filter(private_message::read.eq(false))
.filter(private_message::recipient_id.eq(my_person_id)) .filter(private_message::recipient_id.eq(my_person_id))
.filter(private_message::deleted.eq(false)) .filter(private_message::deleted.eq(false))

View File

@ -1,17 +1,11 @@
use crate::structs::VoteView; use crate::structs::VoteView;
use diesel::{ use diesel::{result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl};
result::Error,
BoolExpressionMethods,
ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases::creator_community_actions,
newtypes::{CommentId, PostId}, newtypes::{CommentId, PostId},
schema::{comment_like, community_person_ban, person, post, post_like}, schema::{comment_actions, community_actions, person, post, post_actions},
utils::{get_conn, limit_and_offset, DbPool}, utils::{action_query, actions_alias, get_conn, limit_and_offset, DbPool},
}; };
impl VoteView { impl VoteView {
@ -24,24 +18,24 @@ impl VoteView {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let (limit, offset) = limit_and_offset(page, limit)?; let (limit, offset) = limit_and_offset(page, limit)?;
post_like::table action_query(post_actions::like_score)
.inner_join(person::table) .inner_join(person::table)
.inner_join(post::table) .inner_join(post::table)
// Join to community_person_ban to get creator_banned_from_community .left_join(actions_alias(
.left_join( creator_community_actions,
community_person_ban::table.on( post_actions::person_id,
post::community_id post::community_id,
.eq(community_person_ban::community_id) ))
.and(community_person_ban::person_id.eq(post_like::person_id)), .filter(post_actions::post_id.eq(post_id))
),
)
.filter(post_like::post_id.eq(post_id))
.select(( .select((
person::all_columns, person::all_columns,
community_person_ban::community_id.nullable().is_not_null(), creator_community_actions
post_like::score, .field(community_actions::received_ban)
.nullable()
.is_not_null(),
post_actions::like_score.assume_not_null(),
)) ))
.order_by(post_like::score) .order_by(post_actions::like_score)
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)
.load::<Self>(conn) .load::<Self>(conn)
@ -57,24 +51,24 @@ impl VoteView {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let (limit, offset) = limit_and_offset(page, limit)?; let (limit, offset) = limit_and_offset(page, limit)?;
comment_like::table action_query(comment_actions::like_score)
.inner_join(person::table) .inner_join(person::table)
.inner_join(post::table) .inner_join(post::table)
// Join to community_person_ban to get creator_banned_from_community .left_join(actions_alias(
.left_join( creator_community_actions,
community_person_ban::table.on( comment_actions::person_id,
post::community_id post::community_id,
.eq(community_person_ban::community_id) ))
.and(community_person_ban::person_id.eq(comment_like::person_id)), .filter(comment_actions::comment_id.eq(comment_id))
),
)
.filter(comment_like::comment_id.eq(comment_id))
.select(( .select((
person::all_columns, person::all_columns,
community_person_ban::community_id.nullable().is_not_null(), creator_community_actions
comment_like::score, .field(community_actions::received_ban)
.nullable()
.is_not_null(),
comment_actions::like_score.assume_not_null(),
)) ))
.order_by(comment_like::score) .order_by(comment_actions::like_score)
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)
.load::<Self>(conn) .load::<Self>(conn)

View File

@ -3,36 +3,40 @@ use diesel::{
dsl::{exists, not}, dsl::{exists, not},
pg::Pg, pg::Pg,
result::Error, result::Error,
sql_types,
BoolExpressionMethods, BoolExpressionMethods,
BoxableExpression,
ExpressionMethods, ExpressionMethods,
IntoSql,
JoinOnDsl, JoinOnDsl,
NullableExpressionMethods, NullableExpressionMethods,
QueryDsl, QueryDsl,
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases, aliases::{self, creator_community_actions},
newtypes::{CommentReplyId, PersonId}, newtypes::{CommentReplyId, PersonId},
schema::{ schema::{
comment, comment,
comment_actions,
comment_aggregates, comment_aggregates,
comment_like,
comment_reply, comment_reply,
comment_saved,
community, community,
community_follower, community_actions,
community_moderator,
community_person_ban,
local_user, local_user,
person, person,
person_block, person_actions,
post, post,
}, },
source::local_user::LocalUser, source::{community::CommunityFollower, local_user::LocalUser},
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, utils::{
actions,
actions_alias,
get_conn,
limit_and_offset,
DbConn,
DbPool,
ListFn,
Queries,
ReadFn,
},
CommentSortType, CommentSortType,
}; };
@ -40,74 +44,6 @@ fn queries<'a>() -> Queries<
impl ReadFn<'a, CommentReplyView, (CommentReplyId, Option<PersonId>)>, impl ReadFn<'a, CommentReplyView, (CommentReplyId, Option<PersonId>)>,
impl ListFn<'a, CommentReplyView, CommentReplyQuery>, impl ListFn<'a, CommentReplyView, CommentReplyQuery>,
> { > {
let is_creator_banned_from_community = exists(
community_person_ban::table.filter(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
);
let is_local_user_banned_from_community = |person_id| {
exists(
community_person_ban::table.filter(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(person_id)),
),
)
};
let is_saved = |person_id| {
exists(
comment_saved::table.filter(
comment::id
.eq(comment_saved::comment_id)
.and(comment_saved::person_id.eq(person_id)),
),
)
};
let is_community_followed = |person_id| {
community_follower::table
.filter(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id)),
)
.select(community_follower::pending.nullable())
.single_value()
};
let is_creator_blocked = |person_id| {
exists(
person_block::table.filter(
comment::creator_id
.eq(person_block::target_id)
.and(person_block::person_id.eq(person_id)),
),
)
};
let score = |person_id| {
comment_like::table
.filter(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(person_id)),
)
.select(comment_like::score.nullable())
.single_value()
};
let creator_is_moderator = exists(
community_moderator::table.filter(
community::id
.eq(community_moderator::community_id)
.and(community_moderator::person_id.eq(comment::creator_id)),
),
);
let creator_is_admin = exists( let creator_is_admin = exists(
local_user::table.filter( local_user::table.filter(
comment::creator_id comment::creator_id
@ -118,44 +54,6 @@ fn queries<'a>() -> Queries<
let all_joins = move |query: comment_reply::BoxedQuery<'a, Pg>, let all_joins = move |query: comment_reply::BoxedQuery<'a, Pg>,
my_person_id: Option<PersonId>| { my_person_id: Option<PersonId>| {
let is_local_user_banned_from_community_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>,
> = if let Some(person_id) = my_person_id {
Box::new(is_local_user_banned_from_community(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
let score_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::SmallInt>>,
> = if let Some(person_id) = my_person_id {
Box::new(score(person_id))
} else {
Box::new(None::<i16>.into_sql::<sql_types::Nullable<sql_types::SmallInt>>())
};
let subscribed_type_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::Bool>>,
> = if let Some(person_id) = my_person_id {
Box::new(is_community_followed(person_id))
} else {
Box::new(None::<bool>.into_sql::<sql_types::Nullable<sql_types::Bool>>())
};
let is_saved_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
if let Some(person_id) = my_person_id {
Box::new(is_saved(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
let is_creator_blocked_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
if let Some(person_id) = my_person_id {
Box::new(is_creator_blocked(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
query query
.inner_join(comment::table) .inner_join(comment::table)
.inner_join(person::table.on(comment::creator_id.eq(person::id))) .inner_join(person::table.on(comment::creator_id.eq(person::id)))
@ -163,6 +61,22 @@ fn queries<'a>() -> Queries<
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(aliases::person1) .inner_join(aliases::person1)
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
.left_join(actions(comment_actions::table, my_person_id, comment::id))
.left_join(actions(
community_actions::table,
my_person_id,
post::community_id,
))
.left_join(actions(
person_actions::table,
my_person_id,
comment::creator_id,
))
.left_join(actions_alias(
creator_community_actions,
comment::creator_id,
post::community_id,
))
.select(( .select((
comment_reply::all_columns, comment_reply::all_columns,
comment::all_columns, comment::all_columns,
@ -171,14 +85,20 @@ fn queries<'a>() -> Queries<
community::all_columns, community::all_columns,
aliases::person1.fields(person::all_columns), aliases::person1.fields(person::all_columns),
comment_aggregates::all_columns, comment_aggregates::all_columns,
is_creator_banned_from_community, creator_community_actions
is_local_user_banned_from_community_selection, .field(community_actions::received_ban)
creator_is_moderator, .nullable()
.is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()
.is_not_null(),
creator_is_admin, creator_is_admin,
subscribed_type_selection, CommunityFollower::select_subscribed_type(),
is_saved_selection, comment_actions::saved.nullable().is_not_null(),
is_creator_blocked_selection, person_actions::blocked.nullable().is_not_null(),
score_selection, comment_actions::like_score.nullable(),
)) ))
}; };
@ -221,9 +141,7 @@ fn queries<'a>() -> Queries<
}; };
// Don't show replies from blocked persons // Don't show replies from blocked persons
if let Some(my_person_id) = options.my_person_id { query = query.filter(person_actions::blocked.is_null());
query = query.filter(not(is_creator_blocked(my_person_id)));
}
let (limit, offset) = limit_and_offset(options.page, options.limit)?; let (limit, offset) = limit_and_offset(options.page, options.limit)?;
@ -257,13 +175,11 @@ impl CommentReplyView {
let mut query = comment_reply::table let mut query = comment_reply::table
.inner_join(comment::table) .inner_join(comment::table)
.left_join( .left_join(actions(
person_block::table.on( person_actions::table,
comment::creator_id Some(local_user.person_id),
.eq(person_block::target_id) comment::creator_id,
.and(person_block::person_id.eq(local_user.person_id)), ))
),
)
.inner_join(person::table.on(comment::creator_id.eq(person::id))) .inner_join(person::table.on(comment::creator_id.eq(person::id)))
.into_boxed(); .into_boxed();
@ -274,7 +190,7 @@ impl CommentReplyView {
query query
// Don't count replies from blocked users // Don't count replies from blocked users
.filter(person_block::person_id.is_null()) .filter(person_actions::blocked.is_null())
.filter(comment_reply::recipient_id.eq(local_user.person_id)) .filter(comment_reply::recipient_id.eq(local_user.person_id))
.filter(comment_reply::read.eq(false)) .filter(comment_reply::read.eq(false))
.filter(comment::deleted.eq(false)) .filter(comment::deleted.eq(false))

View File

@ -3,21 +3,21 @@ use diesel::{result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::PersonId, newtypes::PersonId,
schema::{community, community_block, person}, schema::{community, community_actions, person},
utils::{get_conn, DbPool}, utils::{action_query, get_conn, DbPool},
}; };
impl CommunityBlockView { impl CommunityBlockView {
pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> { pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
community_block::table action_query(community_actions::blocked)
.inner_join(person::table) .inner_join(person::table)
.inner_join(community::table) .inner_join(community::table)
.select((person::all_columns, community::all_columns)) .select((person::all_columns, community::all_columns))
.filter(community_block::person_id.eq(person_id)) .filter(community_actions::person_id.eq(person_id))
.filter(community::deleted.eq(false)) .filter(community::deleted.eq(false))
.filter(community::removed.eq(false)) .filter(community::removed.eq(false))
.order_by(community_block::published) .order_by(community_actions::blocked)
.load::<CommunityBlockView>(conn) .load::<CommunityBlockView>(conn)
.await .await
} }

View File

@ -9,8 +9,8 @@ use diesel::{
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, newtypes::{CommunityId, DbUrl, InstanceId, PersonId},
schema::{community, community_follower, person}, schema::{community, community_actions, person},
utils::{functions::coalesce, get_conn, DbPool}, utils::{action_query, functions::coalesce, get_conn, DbPool},
}; };
impl CommunityFollowerView { impl CommunityFollowerView {
@ -29,14 +29,14 @@ impl CommunityFollowerView {
// that would work for all instances that support fully shared inboxes. // that would work for all instances that support fully shared inboxes.
// It would be a bit more complicated though to keep it in sync. // It would be a bit more complicated though to keep it in sync.
community_follower::table community_actions::table
.inner_join(community::table) .inner_join(community::table)
.inner_join(person::table) .inner_join(person::table)
.filter(person::instance_id.eq(instance_id)) .filter(person::instance_id.eq(instance_id))
.filter(community::local) // this should be a no-op since community_followers table only has .filter(community::local) // this should be a no-op since community_followers table only has
// local-person+remote-community or remote-person+local-community // local-person+remote-community or remote-person+local-community
.filter(not(person::local)) .filter(not(person::local))
.filter(community_follower::published.gt(published_since.naive_utc())) .filter(community_actions::followed.gt(published_since.naive_utc()))
.select(( .select((
community::id, community::id,
coalesce(person::shared_inbox_url, person::inbox_url), coalesce(person::shared_inbox_url, person::inbox_url),
@ -50,8 +50,8 @@ impl CommunityFollowerView {
community_id: CommunityId, community_id: CommunityId,
) -> Result<Vec<DbUrl>, Error> { ) -> Result<Vec<DbUrl>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let res = community_follower::table let res = action_query(community_actions::followed)
.filter(community_follower::community_id.eq(community_id)) .filter(community_actions::community_id.eq(community_id))
.filter(not(person::local)) .filter(not(person::local))
.inner_join(person::table) .inner_join(person::table)
.select(coalesce(person::shared_inbox_url, person::inbox_url)) .select(coalesce(person::shared_inbox_url, person::inbox_url))
@ -66,8 +66,8 @@ impl CommunityFollowerView {
community_id: CommunityId, community_id: CommunityId,
) -> Result<i64, Error> { ) -> Result<i64, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let res = community_follower::table let res = action_query(community_actions::followed)
.filter(community_follower::community_id.eq(community_id)) .filter(community_actions::community_id.eq(community_id))
.select(count_star()) .select(count_star())
.first::<i64>(conn) .first::<i64>(conn)
.await?; .await?;
@ -77,11 +77,11 @@ impl CommunityFollowerView {
pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> { pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
community_follower::table action_query(community_actions::followed)
.inner_join(community::table) .inner_join(community::table)
.inner_join(person::table) .inner_join(person::table)
.select((community::all_columns, person::all_columns)) .select((community::all_columns, person::all_columns))
.filter(community_follower::person_id.eq(person_id)) .filter(community_actions::person_id.eq(person_id))
.filter(community::deleted.eq(false)) .filter(community::deleted.eq(false))
.filter(community::removed.eq(false)) .filter(community::removed.eq(false))
.order_by(community::title) .order_by(community::title)

View File

@ -3,8 +3,8 @@ use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommunityId, PersonId}, newtypes::{CommunityId, PersonId},
schema::{community, community_moderator, person}, schema::{community, community_actions, person},
utils::{get_conn, DbPool}, utils::{action_query, find_action, get_conn, DbPool},
CommunityVisibility, CommunityVisibility,
}; };
@ -14,17 +14,11 @@ impl CommunityModeratorView {
find_community_id: CommunityId, find_community_id: CommunityId,
find_person_id: PersonId, find_person_id: PersonId,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
use lemmy_db_schema::schema::community_moderator::dsl::{
community_id,
community_moderator,
person_id,
};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
select(exists( select(exists(find_action(
community_moderator community_actions::became_moderator,
.filter(community_id.eq(find_community_id)) (find_person_id, find_community_id),
.filter(person_id.eq(find_person_id)), )))
))
.get_result::<bool>(conn) .get_result::<bool>(conn)
.await .await
} }
@ -33,10 +27,10 @@ impl CommunityModeratorView {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
find_person_id: PersonId, find_person_id: PersonId,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
use lemmy_db_schema::schema::community_moderator::dsl::{community_moderator, person_id};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
select(exists( select(exists(
community_moderator.filter(person_id.eq(find_person_id)), action_query(community_actions::became_moderator)
.filter(community_actions::person_id.eq(find_person_id)),
)) ))
.get_result::<bool>(conn) .get_result::<bool>(conn)
.await .await
@ -47,12 +41,12 @@ impl CommunityModeratorView {
community_id: CommunityId, community_id: CommunityId,
) -> Result<Vec<Self>, Error> { ) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
community_moderator::table action_query(community_actions::became_moderator)
.inner_join(community::table) .inner_join(community::table)
.inner_join(person::table) .inner_join(person::table)
.filter(community_moderator::community_id.eq(community_id)) .filter(community_actions::community_id.eq(community_id))
.select((community::all_columns, person::all_columns)) .select((community::all_columns, person::all_columns))
.order_by(community_moderator::published) .order_by(community_actions::became_moderator)
.load::<CommunityModeratorView>(conn) .load::<CommunityModeratorView>(conn)
.await .await
} }
@ -63,10 +57,10 @@ impl CommunityModeratorView {
is_authenticated: bool, is_authenticated: bool,
) -> Result<Vec<Self>, Error> { ) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let mut query = community_moderator::table let mut query = action_query(community_actions::became_moderator)
.inner_join(community::table) .inner_join(community::table)
.inner_join(person::table) .inner_join(person::table)
.filter(community_moderator::person_id.eq(person_id)) .filter(community_actions::person_id.eq(person_id))
.filter(community::deleted.eq(false)) .filter(community::deleted.eq(false))
.filter(community::removed.eq(false)) .filter(community::removed.eq(false))
.select((community::all_columns, person::all_columns)) .select((community::all_columns, person::all_columns))
@ -81,16 +75,16 @@ impl CommunityModeratorView {
/// Ideally this should be a group by, but diesel doesn't support it yet /// Ideally this should be a group by, but diesel doesn't support it yet
pub async fn get_community_first_mods(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> { pub async fn get_community_first_mods(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
community_moderator::table action_query(community_actions::became_moderator)
.inner_join(community::table) .inner_join(community::table)
.inner_join(person::table) .inner_join(person::table)
.select((community::all_columns, person::all_columns)) .select((community::all_columns, person::all_columns))
// A hacky workaround instead of group_bys // A hacky workaround instead of group_bys
// https://stackoverflow.com/questions/24042359/how-to-join-only-one-row-in-joined-table-with-postgres // https://stackoverflow.com/questions/24042359/how-to-join-only-one-row-in-joined-table-with-postgres
.distinct_on(community_moderator::community_id) .distinct_on(community_actions::community_id)
.order_by(( .order_by((
community_moderator::community_id, community_actions::community_id,
community_moderator::published, community_actions::became_moderator,
)) ))
.load::<CommunityModeratorView>(conn) .load::<CommunityModeratorView>(conn)
.await .await

View File

@ -1,10 +1,10 @@
use crate::structs::CommunityPersonBanView; use crate::structs::CommunityPersonBanView;
use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl}; use diesel::{dsl::exists, result::Error, select};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommunityId, PersonId}, newtypes::{CommunityId, PersonId},
schema::community_person_ban, schema::community_actions,
utils::{get_conn, DbPool}, utils::{find_action, get_conn, DbPool},
}; };
impl CommunityPersonBanView { impl CommunityPersonBanView {
@ -14,11 +14,10 @@ impl CommunityPersonBanView {
from_community_id: CommunityId, from_community_id: CommunityId,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
select(exists( select(exists(find_action(
community_person_ban::table community_actions::received_ban,
.filter(community_person_ban::community_id.eq(from_community_id)) (from_person_id, from_community_id),
.filter(community_person_ban::person_id.eq(from_person_id)), )))
))
.get_result::<bool>(conn) .get_result::<bool>(conn)
.await .await
} }

View File

@ -4,7 +4,6 @@ use diesel::{
result::Error, result::Error,
BoolExpressionMethods, BoolExpressionMethods,
ExpressionMethods, ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods, NullableExpressionMethods,
PgTextExpressionMethods, PgTextExpressionMethods,
QueryDsl, QueryDsl,
@ -13,16 +12,10 @@ use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
impls::local_user::LocalUserOptionHelper, impls::local_user::LocalUserOptionHelper,
newtypes::{CommunityId, PersonId}, newtypes::{CommunityId, PersonId},
schema::{ schema::{community, community_actions, community_aggregates, instance_actions},
community,
community_aggregates,
community_block,
community_follower,
community_person_ban,
instance_block,
},
source::{community::CommunityFollower, local_user::LocalUser, site::Site}, source::{community::CommunityFollower, local_user::LocalUser, site::Site},
utils::{ utils::{
actions,
fuzzy_search, fuzzy_search,
limit_and_offset, limit_and_offset,
visible_communities_only, visible_communities_only,
@ -41,47 +34,26 @@ fn queries<'a>() -> Queries<
impl ListFn<'a, CommunityView, (CommunityQuery<'a>, &'a Site)>, impl ListFn<'a, CommunityView, (CommunityQuery<'a>, &'a Site)>,
> { > {
let all_joins = |query: community::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| { let all_joins = |query: community::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| {
// The left join below will return None in this case
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
query query
.inner_join(community_aggregates::table) .inner_join(community_aggregates::table)
.left_join( .left_join(actions(
community_follower::table.on( community_actions::table,
community::id my_person_id,
.eq(community_follower::community_id) community::id,
.and(community_follower::person_id.eq(person_id_join)), ))
), .left_join(actions(
) instance_actions::table,
.left_join( my_person_id,
instance_block::table.on( community::instance_id,
community::instance_id ))
.eq(instance_block::instance_id)
.and(instance_block::person_id.eq(person_id_join)),
),
)
.left_join(
community_block::table.on(
community::id
.eq(community_block::community_id)
.and(community_block::person_id.eq(person_id_join)),
),
)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(person_id_join)),
),
)
}; };
let selection = ( let selection = (
community::all_columns, community::all_columns,
CommunityFollower::select_subscribed_type(), CommunityFollower::select_subscribed_type(),
community_block::community_id.nullable().is_not_null(), community_actions::blocked.nullable().is_not_null(),
community_aggregates::all_columns, community_aggregates::all_columns,
community_person_ban::person_id.nullable().is_not_null(), community_actions::received_ban.nullable().is_not_null(),
); );
let not_removed_or_deleted = community::removed let not_removed_or_deleted = community::removed
@ -113,9 +85,6 @@ fn queries<'a>() -> Queries<
let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move { let list = move |mut conn: DbConn<'a>, (options, site): (CommunityQuery<'a>, &'a Site)| async move {
use SortType::*; use SortType::*;
// The left join below will return None in this case
let person_id_join = options.local_user.person_id().unwrap_or(PersonId(-1));
let mut query = all_joins( let mut query = all_joins(
community::table.into_boxed(), community::table.into_boxed(),
options.local_user.person_id(), options.local_user.person_id(),
@ -134,7 +103,7 @@ fn queries<'a>() -> Queries<
query = query.filter(not_removed_or_deleted).filter( query = query.filter(not_removed_or_deleted).filter(
community::hidden community::hidden
.eq(false) .eq(false)
.or(community_follower::person_id.eq(person_id_join)), .or(community_actions::follow_pending.is_not_null()),
); );
} }
@ -159,7 +128,7 @@ fn queries<'a>() -> Queries<
if let Some(listing_type) = options.listing_type { if let Some(listing_type) = options.listing_type {
query = match listing_type { query = match listing_type {
ListingType::Subscribed => query.filter(community_follower::pending.is_not_null()), /* TODO could be this: and(community_follower::person_id.eq(person_id_join)), */ ListingType::Subscribed => query.filter(community_actions::follow_pending.is_not_null()),
ListingType::Local => query.filter(community::local.eq(true)), ListingType::Local => query.filter(community::local.eq(true)),
_ => query, _ => query,
}; };
@ -167,8 +136,8 @@ fn queries<'a>() -> Queries<
// Don't show blocked communities and communities on blocked instances. nsfw communities are // Don't show blocked communities and communities on blocked instances. nsfw communities are
// also hidden (based on profile setting) // also hidden (based on profile setting)
query = query.filter(instance_block::person_id.is_null()); query = query.filter(instance_actions::blocked.is_null());
query = query.filter(community_block::person_id.is_null()); query = query.filter(community_actions::blocked.is_null());
if !(options.local_user.show_nsfw(site) || options.show_nsfw) { if !(options.local_user.show_nsfw(site) || options.show_nsfw) {
query = query.filter(community::nsfw.eq(false)); query = query.filter(community::nsfw.eq(false));
} }

View File

@ -3,14 +3,14 @@ use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMeth
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::PersonId, newtypes::PersonId,
schema::{instance, instance_block, person, site}, schema::{instance, instance_actions, person, site},
utils::{get_conn, DbPool}, utils::{action_query, get_conn, DbPool},
}; };
impl InstanceBlockView { impl InstanceBlockView {
pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> { pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
instance_block::table action_query(instance_actions::blocked)
.inner_join(person::table) .inner_join(person::table)
.inner_join(instance::table) .inner_join(instance::table)
.left_join(site::table.on(site::instance_id.eq(instance::id))) .left_join(site::table.on(site::instance_id.eq(instance::id)))
@ -19,8 +19,8 @@ impl InstanceBlockView {
instance::all_columns, instance::all_columns,
site::all_columns.nullable(), site::all_columns.nullable(),
)) ))
.filter(instance_block::person_id.eq(person_id)) .filter(instance_actions::person_id.eq(person_id))
.order_by(instance_block::published) .order_by(instance_actions::blocked)
.load::<InstanceBlockView>(conn) .load::<InstanceBlockView>(conn)
.await .await
} }

View File

@ -3,8 +3,8 @@ use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::PersonId, newtypes::PersonId,
schema::{person, person_block}, schema::{person, person_actions},
utils::{get_conn, DbPool}, utils::{action_query, get_conn, DbPool},
}; };
impl PersonBlockView { impl PersonBlockView {
@ -12,18 +12,18 @@ impl PersonBlockView {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let target_person_alias = diesel::alias!(person as person1); let target_person_alias = diesel::alias!(person as person1);
person_block::table action_query(person_actions::blocked)
.inner_join(person::table.on(person_block::person_id.eq(person::id))) .inner_join(person::table.on(person_actions::person_id.eq(person::id)))
.inner_join( .inner_join(
target_person_alias.on(person_block::target_id.eq(target_person_alias.field(person::id))), target_person_alias.on(person_actions::target_id.eq(target_person_alias.field(person::id))),
) )
.select(( .select((
person::all_columns, person::all_columns,
target_person_alias.fields(person::all_columns), target_person_alias.fields(person::all_columns),
)) ))
.filter(person_block::person_id.eq(person_id)) .filter(person_actions::person_id.eq(person_id))
.filter(target_person_alias.field(person::deleted).eq(false)) .filter(target_person_alias.field(person::deleted).eq(false))
.order_by(person_block::published) .order_by(person_actions::blocked)
.load::<PersonBlockView>(conn) .load::<PersonBlockView>(conn)
.await .await
} }

View File

@ -3,36 +3,40 @@ use diesel::{
dsl::{exists, not}, dsl::{exists, not},
pg::Pg, pg::Pg,
result::Error, result::Error,
sql_types,
BoolExpressionMethods, BoolExpressionMethods,
BoxableExpression,
ExpressionMethods, ExpressionMethods,
IntoSql,
JoinOnDsl, JoinOnDsl,
NullableExpressionMethods, NullableExpressionMethods,
QueryDsl, QueryDsl,
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases, aliases::{self, creator_community_actions},
newtypes::{PersonId, PersonMentionId}, newtypes::{PersonId, PersonMentionId},
schema::{ schema::{
comment, comment,
comment_actions,
comment_aggregates, comment_aggregates,
comment_like,
comment_saved,
community, community,
community_follower, community_actions,
community_moderator,
community_person_ban,
local_user, local_user,
person, person,
person_block, person_actions,
person_mention, person_mention,
post, post,
}, },
source::local_user::LocalUser, source::{community::CommunityFollower, local_user::LocalUser},
utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, utils::{
actions,
actions_alias,
get_conn,
limit_and_offset,
DbConn,
DbPool,
ListFn,
Queries,
ReadFn,
},
CommentSortType, CommentSortType,
}; };
@ -40,74 +44,6 @@ fn queries<'a>() -> Queries<
impl ReadFn<'a, PersonMentionView, (PersonMentionId, Option<PersonId>)>, impl ReadFn<'a, PersonMentionView, (PersonMentionId, Option<PersonId>)>,
impl ListFn<'a, PersonMentionView, PersonMentionQuery>, impl ListFn<'a, PersonMentionView, PersonMentionQuery>,
> { > {
let is_creator_banned_from_community = exists(
community_person_ban::table.filter(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
);
let is_local_user_banned_from_community = |person_id| {
exists(
community_person_ban::table.filter(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(person_id)),
),
)
};
let is_saved = |person_id| {
exists(
comment_saved::table.filter(
comment::id
.eq(comment_saved::comment_id)
.and(comment_saved::person_id.eq(person_id)),
),
)
};
let is_community_followed = |person_id| {
community_follower::table
.filter(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id)),
)
.select(community_follower::pending.nullable())
.single_value()
};
let is_creator_blocked = |person_id| {
exists(
person_block::table.filter(
comment::creator_id
.eq(person_block::target_id)
.and(person_block::person_id.eq(person_id)),
),
)
};
let score = |person_id| {
comment_like::table
.filter(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(person_id)),
)
.select(comment_like::score.nullable())
.single_value()
};
let creator_is_moderator = exists(
community_moderator::table.filter(
community::id
.eq(community_moderator::community_id)
.and(community_moderator::person_id.eq(comment::creator_id)),
),
);
let creator_is_admin = exists( let creator_is_admin = exists(
local_user::table.filter( local_user::table.filter(
comment::creator_id comment::creator_id
@ -118,43 +54,6 @@ fn queries<'a>() -> Queries<
let all_joins = move |query: person_mention::BoxedQuery<'a, Pg>, let all_joins = move |query: person_mention::BoxedQuery<'a, Pg>,
my_person_id: Option<PersonId>| { my_person_id: Option<PersonId>| {
let is_local_user_banned_from_community_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>,
> = if let Some(person_id) = my_person_id {
Box::new(is_local_user_banned_from_community(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
let score_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::SmallInt>>,
> = if let Some(person_id) = my_person_id {
Box::new(score(person_id))
} else {
Box::new(None::<i16>.into_sql::<sql_types::Nullable<sql_types::SmallInt>>())
};
let subscribed_type_selection: Box<
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::Bool>>,
> = if let Some(person_id) = my_person_id {
Box::new(is_community_followed(person_id))
} else {
Box::new(None::<bool>.into_sql::<sql_types::Nullable<sql_types::Bool>>())
};
let is_saved_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
if let Some(person_id) = my_person_id {
Box::new(is_saved(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
let is_creator_blocked_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
if let Some(person_id) = my_person_id {
Box::new(is_creator_blocked(person_id))
} else {
Box::new(false.into_sql::<sql_types::Bool>())
};
query query
.inner_join(comment::table) .inner_join(comment::table)
.inner_join(person::table.on(comment::creator_id.eq(person::id))) .inner_join(person::table.on(comment::creator_id.eq(person::id)))
@ -162,6 +61,22 @@ fn queries<'a>() -> Queries<
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(aliases::person1) .inner_join(aliases::person1)
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
.left_join(actions(
community_actions::table,
my_person_id,
post::community_id,
))
.left_join(actions(comment_actions::table, my_person_id, comment::id))
.left_join(actions(
person_actions::table,
my_person_id,
comment::creator_id,
))
.left_join(actions_alias(
creator_community_actions,
comment::creator_id,
post::community_id,
))
.select(( .select((
person_mention::all_columns, person_mention::all_columns,
comment::all_columns, comment::all_columns,
@ -170,14 +85,20 @@ fn queries<'a>() -> Queries<
community::all_columns, community::all_columns,
aliases::person1.fields(person::all_columns), aliases::person1.fields(person::all_columns),
comment_aggregates::all_columns, comment_aggregates::all_columns,
is_creator_banned_from_community, creator_community_actions
is_local_user_banned_from_community_selection, .field(community_actions::received_ban)
creator_is_moderator, .nullable()
.is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()
.is_not_null(),
creator_is_admin, creator_is_admin,
subscribed_type_selection, CommunityFollower::select_subscribed_type(),
is_saved_selection, comment_actions::saved.nullable().is_not_null(),
is_creator_blocked_selection, person_actions::blocked.nullable().is_not_null(),
score_selection, comment_actions::like_score.nullable(),
)) ))
}; };
@ -220,9 +141,7 @@ fn queries<'a>() -> Queries<
}; };
// Don't show mentions from blocked persons // Don't show mentions from blocked persons
if let Some(my_person_id) = options.my_person_id { query = query.filter(person_actions::blocked.is_null());
query = query.filter(not(is_creator_blocked(my_person_id)));
}
let (limit, offset) = limit_and_offset(options.page, options.limit)?; let (limit, offset) = limit_and_offset(options.page, options.limit)?;
@ -257,13 +176,11 @@ impl PersonMentionView {
let mut query = person_mention::table let mut query = person_mention::table
.inner_join(comment::table) .inner_join(comment::table)
.left_join( .left_join(actions(
person_block::table.on( person_actions::table,
comment::creator_id Some(local_user.person_id),
.eq(person_block::target_id) comment::creator_id,
.and(person_block::person_id.eq(local_user.person_id)), ))
),
)
.inner_join(person::table.on(comment::creator_id.eq(person::id))) .inner_join(person::table.on(comment::creator_id.eq(person::id)))
.into_boxed(); .into_boxed();
@ -274,7 +191,7 @@ impl PersonMentionView {
query query
// Don't count replies from blocked users // Don't count replies from blocked users
.filter(person_block::person_id.is_null()) .filter(person_actions::blocked.is_null())
.filter(person_mention::recipient_id.eq(local_user.person_id)) .filter(person_mention::recipient_id.eq(local_user.person_id))
.filter(person_mention::read.eq(false)) .filter(person_mention::read.eq(false))
.filter(comment::deleted.eq(false)) .filter(comment::deleted.eq(false))

View File

@ -0,0 +1,320 @@
-- For each new actions table, create tables that are dropped in up.sql, and insert into them
CREATE TABLE comment_saved (
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
comment_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamptz DEFAULT now() NOT NULL,
PRIMARY KEY (person_id, comment_id)
);
INSERT INTO comment_saved (person_id, comment_id, published)
SELECT
person_id,
comment_id,
saved
FROM
comment_actions
WHERE
saved IS NOT NULL;
CREATE TABLE community_block (
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamptz DEFAULT now() NOT NULL,
PRIMARY KEY (person_id, community_id)
);
INSERT INTO community_block (person_id, community_id, published)
SELECT
person_id,
community_id,
blocked
FROM
community_actions
WHERE
blocked IS NOT NULL;
CREATE TABLE community_person_ban (
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamptz DEFAULT now() NOT NULL,
expires timestamptz,
PRIMARY KEY (person_id, community_id)
);
INSERT INTO community_person_ban (community_id, person_id, published, expires)
SELECT
community_id,
person_id,
received_ban,
ban_expires
FROM
community_actions
WHERE
received_ban IS NOT NULL;
CREATE TABLE community_moderator (
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamptz DEFAULT now() NOT NULL,
PRIMARY KEY (person_id, community_id)
);
INSERT INTO community_moderator (community_id, person_id, published)
SELECT
community_id,
person_id,
became_moderator
FROM
community_actions
WHERE
became_moderator IS NOT NULL;
CREATE TABLE person_block (
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
target_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamptz DEFAULT now() NOT NULL,
PRIMARY KEY (person_id, target_id)
);
INSERT INTO person_block (person_id, target_id, published)
SELECT
person_id,
target_id,
blocked
FROM
person_actions
WHERE
blocked IS NOT NULL;
CREATE TABLE person_post_aggregates (
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
read_comments bigint DEFAULT 0 NOT NULL,
published timestamptz NOT NULL,
PRIMARY KEY (person_id, post_id)
);
INSERT INTO person_post_aggregates (person_id, post_id, read_comments, published)
SELECT
person_id,
post_id,
read_comments_amount,
read_comments
FROM
post_actions
WHERE
read_comments IS NOT NULL;
CREATE TABLE post_hide (
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamptz DEFAULT now() NOT NULL,
PRIMARY KEY (person_id, post_id)
);
INSERT INTO post_hide (post_id, person_id, published)
SELECT
post_id,
person_id,
hidden
FROM
post_actions
WHERE
hidden IS NOT NULL;
CREATE TABLE post_like (
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
score smallint NOT NULL,
published timestamptz DEFAULT now() NOT NULL,
PRIMARY KEY (person_id, post_id)
);
INSERT INTO post_like (post_id, person_id, score, published)
SELECT
post_id,
person_id,
like_score,
liked
FROM
post_actions
WHERE
liked IS NOT NULL;
CREATE TABLE post_saved (
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamptz DEFAULT now() NOT NULL,
PRIMARY KEY (person_id, post_id)
);
INSERT INTO post_saved (post_id, person_id, published)
SELECT
post_id,
person_id,
saved
FROM
post_actions
WHERE
saved IS NOT NULL;
-- Do the opposite of the `ALTER TABLE` commands in up.sql
DELETE FROM comment_actions
WHERE liked IS NULL;
DELETE FROM community_actions
WHERE followed IS NULL;
DELETE FROM instance_actions
WHERE blocked IS NULL;
DELETE FROM person_actions
WHERE followed IS NULL;
DELETE FROM post_actions
WHERE read IS NULL;
ALTER TABLE comment_actions RENAME TO comment_like;
ALTER TABLE community_actions RENAME TO community_follower;
ALTER TABLE instance_actions RENAME TO instance_block;
ALTER TABLE person_actions RENAME TO person_follower;
ALTER TABLE post_actions RENAME TO post_read;
ALTER TABLE comment_like RENAME COLUMN liked TO published;
ALTER TABLE comment_like RENAME COLUMN like_score TO score;
ALTER TABLE community_follower RENAME COLUMN followed TO published;
ALTER TABLE community_follower RENAME COLUMN follow_pending TO pending;
ALTER TABLE instance_block RENAME COLUMN blocked TO published;
ALTER TABLE person_follower RENAME COLUMN person_id TO follower_id;
ALTER TABLE person_follower RENAME COLUMN target_id TO person_id;
ALTER TABLE person_follower RENAME COLUMN followed TO published;
ALTER TABLE person_follower RENAME COLUMN follow_pending TO pending;
ALTER TABLE post_read RENAME COLUMN read TO published;
ALTER TABLE comment_like
DROP CONSTRAINT comment_actions_check_liked,
ALTER COLUMN published SET NOT NULL,
ALTER COLUMN published SET DEFAULT now(),
ALTER COLUMN score SET NOT NULL,
ALTER COLUMN post_id SET NOT NULL,
DROP COLUMN saved;
ALTER TABLE community_follower
DROP CONSTRAINT community_actions_check_followed,
DROP CONSTRAINT community_actions_check_received_ban,
ALTER COLUMN published SET NOT NULL,
ALTER COLUMN published SET DEFAULT now(),
ALTER COLUMN pending SET NOT NULL,
-- This `SET DEFAULT` is done for community follow, but not person follow. It's not a mistake
-- in this migration. Believe it or not, `pending` only had a default value in community follow.
ALTER COLUMN pending SET DEFAULT FALSE,
DROP COLUMN blocked,
DROP COLUMN became_moderator,
DROP COLUMN received_ban,
DROP COLUMN ban_expires;
ALTER TABLE instance_block
ALTER COLUMN published SET NOT NULL,
ALTER COLUMN published SET DEFAULT now();
ALTER TABLE person_follower
DROP CONSTRAINT person_actions_check_followed,
ALTER COLUMN published SET NOT NULL,
ALTER COLUMN published SET DEFAULT now(),
ALTER COLUMN pending SET NOT NULL,
DROP COLUMN blocked;
ALTER TABLE post_read
DROP CONSTRAINT post_actions_check_read_comments,
DROP CONSTRAINT post_actions_check_liked,
ALTER COLUMN published SET NOT NULL,
ALTER COLUMN published SET DEFAULT now(),
DROP COLUMN read_comments,
DROP COLUMN read_comments_amount,
DROP COLUMN saved,
DROP COLUMN liked,
DROP COLUMN like_score,
DROP COLUMN hidden;
-- Rename associated stuff
ALTER INDEX comment_actions_pkey RENAME TO comment_like_pkey;
ALTER INDEX idx_comment_actions_comment RENAME TO idx_comment_like_comment;
ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_comment_id_fkey TO comment_like_comment_id_fkey;
ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_person_id_fkey TO comment_like_person_id_fkey;
ALTER INDEX community_actions_pkey RENAME TO community_follower_pkey;
ALTER INDEX idx_community_actions_community RENAME TO idx_community_follower_community;
ALTER TABLE community_follower RENAME CONSTRAINT community_actions_community_id_fkey TO community_follower_community_id_fkey;
ALTER TABLE community_follower RENAME CONSTRAINT community_actions_person_id_fkey TO community_follower_person_id_fkey;
ALTER INDEX instance_actions_pkey RENAME TO instance_block_pkey;
ALTER TABLE instance_block RENAME CONSTRAINT instance_actions_instance_id_fkey TO instance_block_instance_id_fkey;
ALTER TABLE instance_block RENAME CONSTRAINT instance_actions_person_id_fkey TO instance_block_person_id_fkey;
ALTER INDEX person_actions_pkey RENAME TO person_follower_pkey;
ALTER TABLE person_follower RENAME CONSTRAINT person_actions_target_id_fkey TO person_follower_person_id_fkey;
ALTER TABLE person_follower RENAME CONSTRAINT person_actions_person_id_fkey TO person_follower_follower_id_fkey;
ALTER INDEX post_actions_pkey RENAME TO post_read_pkey;
ALTER TABLE post_read RENAME CONSTRAINT post_actions_person_id_fkey TO post_read_person_id_fkey;
ALTER TABLE post_read RENAME CONSTRAINT post_actions_post_id_fkey TO post_read_post_id_fkey;
-- Rename idx_community_actions_followed and remove filter
CREATE INDEX idx_community_follower_published ON community_follower (published);
DROP INDEX idx_community_actions_followed;
-- Move indexes back to their original tables
CREATE INDEX idx_comment_saved_comment ON comment_saved (comment_id);
CREATE INDEX idx_comment_saved_person ON comment_saved (person_id);
CREATE INDEX idx_community_block_community ON community_block (community_id);
CREATE INDEX idx_community_moderator_community ON community_moderator (community_id);
CREATE INDEX idx_community_moderator_published ON community_moderator (published);
CREATE INDEX idx_person_block_person ON person_block (person_id);
CREATE INDEX idx_person_block_target ON person_block (target_id);
CREATE INDEX idx_person_post_aggregates_person ON person_post_aggregates (person_id);
CREATE INDEX idx_person_post_aggregates_post ON person_post_aggregates (post_id);
CREATE INDEX idx_post_like_post ON post_like (post_id);
DROP INDEX idx_person_actions_person, idx_person_actions_target, idx_post_actions_person, idx_post_actions_post;
-- Drop `NOT NULL` indexes of columns that still exist
DROP INDEX idx_comment_actions_liked_not_null, idx_community_actions_followed_not_null, idx_person_actions_followed_not_null, idx_post_actions_read_not_null, idx_instance_actions_blocked_not_null;
-- Drop statistics of columns that still exist
DROP statistics comment_actions_liked_stat, community_actions_followed_stat, person_actions_followed_stat;

View File

@ -0,0 +1,340 @@
-- For each new actions table, transform the table previously used for the most common action type
-- into the new actions table, which should only change the table's metadata instead of rewriting the
-- rows
ALTER TABLE comment_like RENAME TO comment_actions;
ALTER TABLE community_follower RENAME TO community_actions;
ALTER TABLE instance_block RENAME TO instance_actions;
ALTER TABLE person_follower RENAME TO person_actions;
ALTER TABLE post_read RENAME TO post_actions;
ALTER TABLE comment_actions RENAME COLUMN published TO liked;
ALTER TABLE comment_actions RENAME COLUMN score TO like_score;
ALTER TABLE community_actions RENAME COLUMN published TO followed;
ALTER TABLE community_actions RENAME COLUMN pending TO follow_pending;
ALTER TABLE instance_actions RENAME COLUMN published TO blocked;
ALTER TABLE person_actions RENAME COLUMN person_id TO target_id;
ALTER TABLE person_actions RENAME COLUMN follower_id TO person_id;
ALTER TABLE person_actions RENAME COLUMN published TO followed;
ALTER TABLE person_actions RENAME COLUMN pending TO follow_pending;
ALTER TABLE post_actions RENAME COLUMN published TO read;
ALTER TABLE comment_actions
ALTER COLUMN post_id DROP NOT NULL,
ALTER COLUMN liked DROP NOT NULL,
ALTER COLUMN liked DROP DEFAULT,
ALTER COLUMN like_score DROP NOT NULL,
ADD COLUMN saved timestamptz,
-- `post_id` was only in the `comment_liked` table, and removing it entirely or making it not null
-- for the `saved` action would make this PR too complicated
ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = ALL (ARRAY[like_score IS NULL, post_id IS NULL]));
ALTER TABLE community_actions
ALTER COLUMN followed DROP NOT NULL,
ALTER COLUMN followed DROP DEFAULT,
ALTER COLUMN follow_pending DROP NOT NULL,
-- This `DROP DEFAULT` is done for community follow, but not person follow. It's not a mistake
-- in this migration. Believe it or not, `pending` only had a default value in community follow.
ALTER COLUMN follow_pending DROP DEFAULT,
ADD COLUMN blocked timestamptz,
ADD COLUMN became_moderator timestamptz,
ADD COLUMN received_ban timestamptz,
ADD COLUMN ban_expires timestamptz,
ADD CONSTRAINT community_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL)),
ADD CONSTRAINT community_actions_check_received_ban CHECK (NOT (received_ban IS NULL AND ban_expires IS NOT NULL));
ALTER TABLE instance_actions
ALTER COLUMN blocked DROP NOT NULL,
ALTER COLUMN blocked DROP DEFAULT;
ALTER TABLE person_actions
ALTER COLUMN followed DROP NOT NULL,
ALTER COLUMN followed DROP DEFAULT,
ALTER COLUMN follow_pending DROP NOT NULL,
ADD COLUMN blocked timestamptz,
ADD CONSTRAINT person_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL));
ALTER TABLE post_actions
ALTER COLUMN read DROP NOT NULL,
ALTER COLUMN read DROP DEFAULT,
ADD COLUMN read_comments timestamptz,
ADD COLUMN read_comments_amount bigint,
ADD COLUMN saved timestamptz,
ADD COLUMN liked timestamptz,
ADD COLUMN like_score smallint,
ADD COLUMN hidden timestamptz,
ADD CONSTRAINT post_actions_check_read_comments CHECK ((read_comments IS NULL) = (read_comments_amount IS NULL)),
ADD CONSTRAINT post_actions_check_liked CHECK ((liked IS NULL) = (like_score IS NULL));
-- Add actions from other old tables to the new tables
INSERT INTO comment_actions (person_id, comment_id, saved)
SELECT
person_id,
comment_id,
published
FROM
comment_saved
ON CONFLICT (person_id,
comment_id)
DO UPDATE SET
saved = excluded.saved;
INSERT INTO community_actions (person_id, community_id, blocked)
SELECT
person_id,
community_id,
published
FROM
community_block
ON CONFLICT (person_id,
community_id)
DO UPDATE SET
person_id = excluded.person_id,
community_id = excluded.community_id,
blocked = excluded.blocked;
INSERT INTO community_actions (person_id, community_id, became_moderator)
SELECT
person_id,
community_id,
published
FROM
community_moderator
ON CONFLICT (person_id,
community_id)
DO UPDATE SET
person_id = excluded.person_id,
community_id = excluded.community_id,
became_moderator = excluded.became_moderator;
INSERT INTO community_actions (person_id, community_id, received_ban, ban_expires)
SELECT
person_id,
community_id,
published,
expires
FROM
community_person_ban
ON CONFLICT (person_id,
community_id)
DO UPDATE SET
person_id = excluded.person_id,
community_id = excluded.community_id,
received_ban = excluded.received_ban,
ban_expires = excluded.ban_expires;
INSERT INTO person_actions (person_id, target_id, blocked)
SELECT
person_id,
target_id,
published
FROM
person_block
ON CONFLICT (person_id,
target_id)
DO UPDATE SET
person_id = excluded.person_id,
target_id = excluded.target_id,
blocked = excluded.blocked;
INSERT INTO post_actions (person_id, post_id, read_comments, read_comments_amount)
SELECT
person_id,
post_id,
published,
read_comments
FROM
person_post_aggregates
ON CONFLICT (person_id,
post_id)
DO UPDATE SET
read_comments = excluded.read_comments,
read_comments_amount = excluded.read_comments_amount;
INSERT INTO post_actions (person_id, post_id, hidden)
SELECT
person_id,
post_id,
published
FROM
post_hide
ON CONFLICT (person_id,
post_id)
DO UPDATE SET
hidden = excluded.hidden;
INSERT INTO post_actions (person_id, post_id, liked, like_score)
SELECT
person_id,
post_id,
published,
score
FROM
post_like
ON CONFLICT (person_id,
post_id)
DO UPDATE SET
liked = excluded.liked,
like_score = excluded.like_score;
INSERT INTO post_actions (person_id, post_id, saved)
SELECT
person_id,
post_id,
published
FROM
post_saved
ON CONFLICT (person_id,
post_id)
DO UPDATE SET
saved = excluded.saved;
-- Drop old tables
DROP TABLE comment_saved, community_block, community_moderator, community_person_ban, person_block, person_post_aggregates, post_hide, post_like, post_saved;
-- Rename associated stuff
ALTER INDEX comment_like_pkey RENAME TO comment_actions_pkey;
ALTER INDEX idx_comment_like_comment RENAME TO idx_comment_actions_comment;
ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_comment_id_fkey TO comment_actions_comment_id_fkey;
ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_person_id_fkey TO comment_actions_person_id_fkey;
ALTER INDEX community_follower_pkey RENAME TO community_actions_pkey;
ALTER INDEX idx_community_follower_community RENAME TO idx_community_actions_community;
ALTER TABLE community_actions RENAME CONSTRAINT community_follower_community_id_fkey TO community_actions_community_id_fkey;
ALTER TABLE community_actions RENAME CONSTRAINT community_follower_person_id_fkey TO community_actions_person_id_fkey;
ALTER INDEX instance_block_pkey RENAME TO instance_actions_pkey;
ALTER TABLE instance_actions RENAME CONSTRAINT instance_block_instance_id_fkey TO instance_actions_instance_id_fkey;
ALTER TABLE instance_actions RENAME CONSTRAINT instance_block_person_id_fkey TO instance_actions_person_id_fkey;
ALTER INDEX person_follower_pkey RENAME TO person_actions_pkey;
ALTER TABLE person_actions RENAME CONSTRAINT person_follower_person_id_fkey TO person_actions_target_id_fkey;
ALTER TABLE person_actions RENAME CONSTRAINT person_follower_follower_id_fkey TO person_actions_person_id_fkey;
ALTER INDEX post_read_pkey RENAME TO post_actions_pkey;
ALTER TABLE post_actions RENAME CONSTRAINT post_read_person_id_fkey TO post_actions_person_id_fkey;
ALTER TABLE post_actions RENAME CONSTRAINT post_read_post_id_fkey TO post_actions_post_id_fkey;
-- Rename idx_community_follower_published and add filter
CREATE INDEX idx_community_actions_followed ON community_actions (followed)
WHERE
followed IS NOT NULL;
DROP INDEX idx_community_follower_published;
-- Restore indexes of dropped tables
CREATE INDEX idx_community_actions_became_moderator ON community_actions (became_moderator)
WHERE
became_moderator IS NOT NULL;
CREATE INDEX idx_person_actions_person ON person_actions (person_id);
CREATE INDEX idx_person_actions_target ON person_actions (target_id);
CREATE INDEX idx_post_actions_person ON post_actions (person_id);
CREATE INDEX idx_post_actions_post ON post_actions (post_id);
-- Create new indexes, with `OR` being used to allow `IS NOT NULL` filters in queries to use either column in
-- a group (e.g. `liked IS NOT NULL` and `like_score IS NOT NULL` both work)
CREATE INDEX idx_comment_actions_liked_not_null ON comment_actions (person_id, comment_id)
WHERE
liked IS NOT NULL OR like_score IS NOT NULL;
CREATE INDEX idx_comment_actions_saved_not_null ON comment_actions (person_id, comment_id)
WHERE
saved IS NOT NULL;
CREATE INDEX idx_community_actions_followed_not_null ON community_actions (person_id, community_id)
WHERE
followed IS NOT NULL OR follow_pending IS NOT NULL;
CREATE INDEX idx_community_actions_blocked_not_null ON community_actions (person_id, community_id)
WHERE
blocked IS NOT NULL;
CREATE INDEX idx_community_actions_became_moderator_not_null ON community_actions (person_id, community_id)
WHERE
became_moderator IS NOT NULL;
CREATE INDEX idx_community_actions_received_ban_not_null ON community_actions (person_id, community_id)
WHERE
received_ban IS NOT NULL;
CREATE INDEX idx_person_actions_followed_not_null ON person_actions (person_id, target_id)
WHERE
followed IS NOT NULL OR follow_pending IS NOT NULL;
CREATE INDEX idx_person_actions_blocked_not_null ON person_actions (person_id, target_id)
WHERE
blocked IS NOT NULL;
CREATE INDEX idx_post_actions_read_not_null ON post_actions (person_id, post_id)
WHERE
read IS NOT NULL;
CREATE INDEX idx_post_actions_read_comments_not_null ON post_actions (person_id, post_id)
WHERE
read_comments IS NOT NULL OR read_comments IS NOT NULL;
CREATE INDEX idx_post_actions_saved_not_null ON post_actions (person_id, post_id)
WHERE
saved IS NOT NULL;
CREATE INDEX idx_post_actions_liked_not_null ON post_actions (person_id, post_id)
WHERE
liked IS NOT NULL OR like_score IS NOT NULL;
CREATE INDEX idx_post_actions_hidden_not_null ON post_actions (person_id, post_id)
WHERE
hidden IS NOT NULL;
-- This index is currently redundant because instance_actions only has 1 action type, but inconsistency
-- with other tables would make it harder to do everything correctly when adding another action type
CREATE INDEX idx_instance_actions_blocked_not_null ON instance_actions (person_id, instance_id)
WHERE
blocked IS NOT NULL;
-- Create new statistics for more accurate estimations of how much of an index will be read (e.g. for
-- `(liked, like_score)`, the query planner might othewise assume that `(TRUE, FALSE)` and `(TRUE, TRUE)`
-- are equally likely when only `(TRUE, TRUE)` is possible, which would make it severely underestimate
-- the efficiency of using the index)
CREATE statistics comment_actions_liked_stat ON (liked IS NULL), (like_score IS NULL)
FROM comment_actions;
CREATE statistics community_actions_followed_stat ON (followed IS NULL), (follow_pending IS NULL)
FROM community_actions;
CREATE statistics person_actions_followed_stat ON (followed IS NULL), (follow_pending IS NULL)
FROM person_actions;
CREATE statistics post_actions_read_comments_stat ON (read_comments IS NULL), (read_comments_amount IS NULL)
FROM post_actions;
CREATE statistics post_actions_liked_stat ON (liked IS NULL), (like_score IS NULL), (post_id IS NULL)
FROM post_actions;

View File

@ -15,7 +15,7 @@ use lemmy_db_schema::{
schema::{ schema::{
captcha_answer, captcha_answer,
comment, comment,
community_person_ban, community_actions,
instance, instance,
person, person,
post, post,
@ -437,7 +437,7 @@ async fn update_banned_when_expired(pool: &mut DbPool<'_>) {
.ok(); .ok();
diesel::delete( diesel::delete(
community_person_ban::table.filter(community_person_ban::expires.lt(now().nullable())), community_actions::table.filter(community_actions::ban_expires.lt(now().nullable())),
) )
.execute(&mut conn) .execute(&mut conn)
.await .await