diff --git a/Cargo.lock b/Cargo.lock index a51c63436..a961d4388 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1280,15 +1280,6 @@ dependencies = [ "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]] name = "diesel-derive-enum" version = "2.1.0" @@ -2701,7 +2692,6 @@ dependencies = [ "derive-new", "diesel", "diesel-async", - "diesel-bind-if-some", "diesel-derive-enum", "diesel-derive-newtype", "diesel_ltree", @@ -2746,7 +2736,6 @@ dependencies = [ "serde_json", "serde_with", "serial_test", - "strum", "test-context", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 7d5acc7ea..ab9d9525d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -157,7 +157,6 @@ i-love-jesus = { version = "0.1.0" } clap = { version = "4.5.27", features = ["derive", "env"] } pretty_assertions = "1.4.1" derive-new = "0.7.0" -diesel-bind-if-some = "0.1.0" tuplex = "0.1.2" [dependencies] diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index cf260f326..b2b1dbb1b 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -38,7 +38,6 @@ full = [ "rustls", "i-love-jesus", "tuplex", - "diesel-bind-if-some", ] [dependencies] @@ -78,7 +77,6 @@ rustls = { workspace = true, optional = true } uuid.workspace = true i-love-jesus = { workspace = true, optional = true } anyhow = { workspace = true } -diesel-bind-if-some = { workspace = true, optional = true } derive-new.workspace = true tuplex = { workspace = true, optional = true } diff --git a/crates/db_schema/src/aggregates/person_post_aggregates.rs b/crates/db_schema/src/aggregates/person_post_aggregates.rs index 63a50af9c..9f40b71b8 100644 --- a/crates/db_schema/src/aggregates/person_post_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_post_aggregates.rs @@ -3,7 +3,7 @@ use crate::{ diesel::OptionalExtension, newtypes::{PersonId, PostId}, schema::post_actions, - utils::{find_action, get_conn, now, DbPool}, + utils::{get_conn, now, DbPool}, }; use diesel::{ expression::SelectableHelper, @@ -37,7 +37,9 @@ impl PersonPostAggregates { post_id_: PostId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - find_action(post_actions::read_comments, (person_id_, post_id_)) + post_actions::table + .find((person_id_, post_id_)) + .filter(post_actions::read_comments.is_not_null()) .select(Self::as_select()) .first(conn) .await diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 059565d6b..b4748d196 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -20,8 +20,6 @@ use crate::{ }, traits::{ApubActor, Bannable, Crud, Followable, Joinable}, utils::{ - action_query, - find_action, functions::{coalesce, coalesce_2_nullable, lower, random_smallint}, get_conn, now, @@ -268,6 +266,13 @@ impl Community { .get_result::(conn) .await } + + #[diesel::dsl::auto_type(no_type_alias)] + pub fn hide_removed_and_deleted() -> _ { + community::removed + .eq(false) + .and(community::deleted.eq(false)) + } } impl CommunityModerator { @@ -301,7 +306,8 @@ impl CommunityModerator { for_person_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - action_query(community_actions::became_moderator) + community_actions::table + .filter(community_actions::became_moderator.is_not_null()) .filter(community_actions::person_id.eq(for_person_id)) .select(community_actions::community_id) .load::(conn) @@ -322,7 +328,8 @@ impl CommunityModerator { persons.push(mod_person_id); persons.dedup(); - let res = action_query(community_actions::became_moderator) + let res = community_actions::table + .filter(community_actions::became_moderator.is_not_null()) .filter(community_actions::community_id.eq(for_community_id)) .filter(community_actions::person_id.eq_any(persons)) .order_by(community_actions::became_moderator) @@ -393,14 +400,14 @@ impl CommunityFollower { remote_community_id: CommunityId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists( - action_query(community_actions::followed) - .filter(community_actions::community_id.eq(remote_community_id)), - )) - .get_result::(conn) - .await? - .then_some(()) - .ok_or(LemmyErrorType::CommunityHasNoFollowers.into()) + let find_action = community_actions::table + .filter(community_actions::followed.is_not_null()) + .filter(community_actions::community_id.eq(remote_community_id)); + select(exists(find_action)) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::CommunityHasNoFollowers.into()) } pub async fn approve( @@ -410,16 +417,16 @@ impl CommunityFollower { approver_id: PersonId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - diesel::update(find_action( - community_actions::followed, - (follower_id, community_id), - )) - .set(( - community_actions::follow_state.eq(CommunityFollowerState::Accepted), - community_actions::follow_approver_id.eq(approver_id), - )) - .execute(conn) - .await?; + let find_action = community_actions::table + .find((follower_id, community_id)) + .filter(community_actions::followed.is_not_null()); + diesel::update(find_action) + .set(( + community_actions::follow_state.eq(CommunityFollowerState::Accepted), + community_actions::follow_approver_id.eq(approver_id), + )) + .execute(conn) + .await?; Ok(()) } } @@ -462,14 +469,14 @@ impl Followable for CommunityFollower { person_id: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(find_action( - community_actions::follow_state, - (person_id, community_id), - )) - .set(community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted))) - .returning(Self::as_select()) - .get_result::(conn) - .await + let find_action = community_actions::table + .find((person_id, community_id)) + .filter(community_actions::follow_state.is_not_null()); + diesel::update(find_action) + .set(community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted))) + .returning(Self::as_select()) + .get_result::(conn) + .await } async fn unfollow( pool: &mut DbPool<'_>, @@ -510,9 +517,7 @@ impl ApubActor for Community { .filter(community::local.eq(true)) .filter(lower(community::name).eq(community_name.to_lowercase())); if !include_deleted { - q = q - .filter(community::deleted.eq(false)) - .filter(community::removed.eq(false)); + q = q.filter(Self::hide_removed_and_deleted()) } q.first(conn).await.optional() } diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index c520e43e8..9217e2105 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -6,7 +6,7 @@ use crate::{ community_block::{CommunityBlock, CommunityBlockForm}, }, traits::Blockable, - utils::{action_query, find_action, get_conn, now, uplete, DbPool}, + utils::{get_conn, now, uplete, DbPool}, }; use diesel::{ dsl::{exists, insert_into, not}, @@ -27,14 +27,14 @@ impl CommunityBlock { for_community_id: CommunityId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(not(exists(find_action( - community_actions::blocked, - (for_person_id, for_community_id), - )))) - .get_result::(conn) - .await? - .then_some(()) - .ok_or(LemmyErrorType::CommunityIsBlocked.into()) + let find_action = community_actions::table + .find((for_person_id, for_community_id)) + .filter(community_actions::blocked.is_not_null()); + select(not(exists(find_action))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::CommunityIsBlocked.into()) } pub async fn for_person( @@ -42,7 +42,8 @@ impl CommunityBlock { person_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - action_query(community_actions::blocked) + community_actions::table + .filter(community_actions::blocked.is_not_null()) .inner_join(community::table) .select(community::all_columns) .filter(community_actions::person_id.eq(person_id)) diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index 1722e8318..1ec6d2201 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -6,7 +6,7 @@ use crate::{ instance_block::{InstanceBlock, InstanceBlockForm}, }, traits::Blockable, - utils::{action_query, find_action, get_conn, now, uplete, DbPool}, + utils::{get_conn, now, uplete, DbPool}, }; use diesel::{ dsl::{exists, insert_into, not}, @@ -27,14 +27,14 @@ impl InstanceBlock { for_instance_id: InstanceId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(not(exists(find_action( - instance_actions::blocked, - (for_person_id, for_instance_id), - )))) - .get_result::(conn) - .await? - .then_some(()) - .ok_or(LemmyErrorType::InstanceIsBlocked.into()) + let find_action = instance_actions::table + .find((for_person_id, for_instance_id)) + .filter(instance_actions::blocked.is_not_null()); + select(not(exists(find_action))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::InstanceIsBlocked.into()) } pub async fn for_person( @@ -42,7 +42,8 @@ impl InstanceBlock { person_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - action_query(instance_actions::blocked) + instance_actions::table + .filter(instance_actions::blocked.is_not_null()) .inner_join(instance::table) .select(instance::all_columns) .filter(instance_actions::person_id.eq(person_id)) diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index 5c184113e..e6c02ced1 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -8,7 +8,6 @@ use crate::{ site::Site, }, utils::{ - action_query, functions::{coalesce, lower}, get_conn, now, @@ -168,42 +167,48 @@ impl LocalUser { }; let conn = &mut get_conn(pool).await?; - let followed_communities = action_query(community_actions::followed) + let followed_communities = community_actions::table + .filter(community_actions::followed.is_not_null()) .filter(community_actions::person_id.eq(person_id_)) .inner_join(community::table) .select(community::actor_id) .get_results(conn) .await?; - let saved_posts = action_query(post_actions::saved) + let saved_posts = post_actions::table + .filter(post_actions::saved.is_not_null()) .filter(post_actions::person_id.eq(person_id_)) .inner_join(post::table) .select(post::ap_id) .get_results(conn) .await?; - let saved_comments = action_query(comment_actions::saved) + let saved_comments = comment_actions::table + .filter(comment_actions::saved.is_not_null()) .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) + let blocked_communities = community_actions::table + .filter(community_actions::blocked.is_not_null()) .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) + let blocked_users = person_actions::table + .filter(person_actions::blocked.is_not_null()) .filter(person_actions::person_id.eq(person_id_)) .inner_join(person::table.on(person_actions::target_id.eq(person::id))) .select(person::actor_id) .get_results(conn) .await?; - let blocked_instances = action_query(instance_actions::blocked) + let blocked_instances = instance_actions::table + .filter(instance_actions::blocked.is_not_null()) .filter(instance_actions::person_id.eq(person_id_)) .inner_join(instance::table) .select(instance::domain) @@ -271,7 +276,8 @@ impl LocalUser { .order_by(local_user::id) .select(local_user::person_id); - let mods = action_query(community_actions::became_moderator) + let mods = community_actions::table + .filter(community_actions::became_moderator.is_not_null()) .filter(community_actions::community_id.eq(for_community_id)) .filter(community_actions::person_id.eq_any(&persons)) .order_by(community_actions::became_moderator) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index fb8c96f04..70e2f7f8d 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -10,7 +10,7 @@ use crate::{ PersonUpdateForm, }, traits::{ApubActor, Crud, Followable}, - utils::{action_query, functions::lower, get_conn, now, uplete, DbPool}, + utils::{functions::lower, get_conn, now, uplete, DbPool}, }; use chrono::Utc; use diesel::{ @@ -235,7 +235,8 @@ impl PersonFollower { for_person_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - action_query(person_actions::followed) + person_actions::table + .filter(person_actions::followed.is_not_null()) .inner_join(person::table.on(person_actions::person_id.eq(person::id))) .filter(person_actions::target_id.eq(for_person_id)) .select(person::all_columns) diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index 363a2d3d1..91f45494c 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -6,7 +6,7 @@ use crate::{ person_block::{PersonBlock, PersonBlockForm}, }, traits::Blockable, - utils::{action_query, find_action, get_conn, now, uplete, DbPool}, + utils::{get_conn, now, uplete, DbPool}, }; use diesel::{ dsl::{exists, insert_into, not}, @@ -28,14 +28,14 @@ impl PersonBlock { for_recipient_id: PersonId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(not(exists(find_action( - person_actions::blocked, - (for_person_id, for_recipient_id), - )))) - .get_result::(conn) - .await? - .then_some(()) - .ok_or(LemmyErrorType::PersonIsBlocked.into()) + let find_action = person_actions::table + .find((for_person_id, for_recipient_id)) + .filter(person_actions::blocked.is_not_null()); + select(not(exists(find_action))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::PersonIsBlocked.into()) } pub async fn for_person( @@ -45,7 +45,8 @@ impl PersonBlock { let conn = &mut get_conn(pool).await?; let target_person_alias = diesel::alias!(person as person1); - action_query(person_actions::blocked) + person_actions::table + .filter(person_actions::blocked.is_not_null()) .inner_join(person::table.on(person_actions::person_id.eq(person::id))) .inner_join( target_person_alias.on(person_actions::target_id.eq(target_person_alias.field(person::id))), diff --git a/crates/db_schema/src/impls/registration_application.rs b/crates/db_schema/src/impls/registration_application.rs index d9777919d..9faa1235d 100644 --- a/crates/db_schema/src/impls/registration_application.rs +++ b/crates/db_schema/src/impls/registration_application.rs @@ -1,6 +1,6 @@ use crate::{ newtypes::{LocalUserId, RegistrationApplicationId}, - schema::registration_application::dsl::{local_user_id, registration_application}, + schema::registration_application, source::registration_application::{ RegistrationApplication, RegistrationApplicationInsertForm, @@ -20,7 +20,7 @@ impl Crud for RegistrationApplication { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { let conn = &mut get_conn(pool).await?; - insert_into(registration_application) + insert_into(registration_application::table) .values(form) .get_result::(conn) .await @@ -32,7 +32,7 @@ impl Crud for RegistrationApplication { form: &Self::UpdateForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(registration_application.find(id_)) + diesel::update(registration_application::table.find(id_)) .set(form) .get_result::(conn) .await @@ -45,9 +45,15 @@ impl RegistrationApplication { local_user_id_: LocalUserId, ) -> Result { let conn = &mut get_conn(pool).await?; - registration_application - .filter(local_user_id.eq(local_user_id_)) + registration_application::table + .filter(registration_application::local_user_id.eq(local_user_id_)) .first(conn) .await } + + /// A missing admin id, means the application is unread + #[diesel::dsl::auto_type(no_type_alias)] + pub fn is_unread() -> _ { + registration_application::admin_id.is_null() + } } diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 980e6ac8d..c5fa14f5a 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -1,5 +1,3 @@ -#![recursion_limit = "256"] - #[cfg(feature = "full")] #[macro_use] extern crate diesel; @@ -41,6 +39,10 @@ pub mod utils; #[cfg(feature = "full")] pub mod schema_setup; +#[cfg(feature = "full")] +use diesel::query_source::AliasedField; +#[cfg(feature = "full")] +use schema::person; use serde::{Deserialize, Serialize}; use strum::{Display, EnumString}; #[cfg(feature = "full")] @@ -328,3 +330,28 @@ macro_rules! assert_length { assert_eq!($len, $vec.len(), "Vec has wrong length: {:?}", $vec) }}; } + +#[cfg(feature = "full")] +/// A helper tuple for person alias columns +pub type Person1AliasAllColumnsTuple = ( + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, + AliasedField, +); diff --git a/crates/db_schema/src/source/local_site.rs b/crates/db_schema/src/source/local_site.rs index 25ec40ca9..46b92d340 100644 --- a/crates/db_schema/src/source/local_site.rs +++ b/crates/db_schema/src/source/local_site.rs @@ -17,7 +17,7 @@ use ts_rs::TS; #[skip_serializing_none] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, Default)] -#[cfg_attr(feature = "full", derive(Queryable, Identifiable, TS))] +#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))] #[cfg_attr(feature = "full", diesel(table_name = local_site))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::site::Site)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 312f219e1..7597c5bad 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -9,22 +9,15 @@ use diesel::{ helper_types::AsExprOf, pg::Pg, query_builder::{Query, QueryFragment}, - query_dsl::methods::{FilterDsl, FindDsl, LimitDsl}, - query_source::{Alias, AliasSource, AliasedField}, + query_dsl::methods::LimitDsl, result::{ ConnectionError, ConnectionResult, Error::{self as DieselError, QueryBuilderError}, }, - sql_types::{self, SingleValue, Timestamptz}, - Column, + sql_types::{self, Timestamptz}, Expression, - ExpressionMethods, IntoSql, - JoinOnDsl, - NullableExpressionMethods, - QuerySource, - Table, }; use diesel_async::{ pg::AsyncPgConnection, @@ -35,8 +28,7 @@ use diesel_async::{ }, AsyncConnection, }; -use diesel_bind_if_some::BindIfSome; -use futures_util::{future::BoxFuture, Future, FutureExt}; +use futures_util::{future::BoxFuture, FutureExt}; use i_love_jesus::{CursorKey, PaginatedQueryBuilder}; use lemmy_utils::{ error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, @@ -580,102 +572,6 @@ impl>> AsRecord for T /// Output of `IntoSql::into_sql` for a type that implements `AsRecord` pub type AsRecordOutput = dsl::AsExprOf::SqlType>>; -/// Output of `t.on((l0, l1).into_sql().eq((r0, r1)))` -type OnTupleEq = dsl::On, (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( - actions_table: T, - person_id: Option

, - target_id: C, -) -> OnTupleEq, K1, BindIfSome>, C> -where - T: Table + Copy, - K0: Expression, - P: AsExpression, - (dsl::Nullable, K1): AsRecord, - (BindIfSome>, C): - AsExpression<, 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( - actions_table: Alias, - person_id: P, - target_id: C, -) -> OnTupleEq, AliasedField, AliasedField, P, C> -where - Alias: QuerySource + Copy, - T: AliasSource> + Default, - K0: Column, - K1: Column
, - (AliasedField, AliasedField): AsRecord, - (P, C): AsExpression< - , AliasedField)> 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(column: C) -> dsl::Filter> -where - C: Column>, 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( - column: C, - key: K, -) -> dsl::Filter, dsl::IsNotNull> -where - C: - Column>>, 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( - column: C, - f: impl FnOnce(C::Table) -> Q, -) -> dsl::Filter> -where - C: Column, - Q: FilterDsl>, -{ - f(C::Table::default()).filter(column.is_not_null()) -} - pub type ResultFuture<'a, T> = BoxFuture<'a, Result>; pub trait ReadFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, T> {} @@ -686,58 +582,6 @@ pub trait ListFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, Vec> impl<'a, T, Args, F: Fn(DbConn<'a>, Args) -> ResultFuture<'a, Vec>> ListFn<'a, T, Args> for F {} -/// Allows read and list functions to capture a shared closure that has an inferred return type, -/// which is useful for join logic -pub struct Queries { - pub read_fn: RF, - pub list_fn: LF, -} - -// `()` is used to prevent type inference error -impl Queries<(), ()> { - pub fn new<'a, RFut, LFut, RT, LT, RA, LA, RF2, LF2>( - read_fn: RF2, - list_fn: LF2, - ) -> Queries, impl ListFn<'a, LT, LA>> - where - RFut: Future> + Sized + Send + 'a, - LFut: Future, DieselError>> + Sized + Send + 'a, - RF2: Fn(DbConn<'a>, RA) -> RFut, - LF2: Fn(DbConn<'a>, LA) -> LFut, - { - Queries { - read_fn: move |conn, args| read_fn(conn, args).boxed(), - list_fn: move |conn, args| list_fn(conn, args).boxed(), - } - } -} - -impl Queries { - pub async fn read<'a, T, Args>( - self, - pool: &'a mut DbPool<'_>, - args: Args, - ) -> Result - where - RF: ReadFn<'a, T, Args>, - { - let conn = get_conn(pool).await?; - (self.read_fn)(conn, args).await - } - - pub async fn list<'a, T, Args>( - self, - pool: &'a mut DbPool<'_>, - args: Args, - ) -> Result, DieselError> - where - LF: ListFn<'a, T, Args>, - { - let conn = get_conn(pool).await?; - (self.list_fn)(conn, args).await - } -} - pub fn paginate( query: Q, page_after: Option, diff --git a/crates/db_views/Cargo.toml b/crates/db_views/Cargo.toml index 1e1f3315d..79b7a80f6 100644 --- a/crates/db_views/Cargo.toml +++ b/crates/db_views/Cargo.toml @@ -41,8 +41,7 @@ ts-rs = { workspace = true, optional = true } actix-web = { workspace = true, optional = true } i-love-jesus = { workspace = true, optional = true } chrono = { workspace = true } -derive-new.workspace = true -strum = { workspace = true } +derive-new = { workspace = true } [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/db_views/src/combined/inbox_combined_view.rs b/crates/db_views/src/combined/inbox_combined_view.rs index 0e51c4ab3..7e7bc1f20 100644 --- a/crates/db_views/src/combined/inbox_combined_view.rs +++ b/crates/db_views/src/combined/inbox_combined_view.rs @@ -49,35 +49,17 @@ use lemmy_db_schema::{ community::CommunityFollower, }, traits::InternalToCombinedView, - utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool}, + utils::{functions::coalesce, get_conn, DbPool}, InboxDataType, }; use lemmy_utils::error::LemmyResult; impl InboxCombinedViewInternal { - /// Gets the number of unread mentions - pub async fn get_unread_count( - pool: &mut DbPool<'_>, - my_person_id: PersonId, - show_bot_accounts: bool, - ) -> Result { - use diesel::dsl::count; - let conn = &mut get_conn(pool).await?; - + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(my_person_id: PersonId) -> _ { let item_creator = person::id; let recipient_person = aliases::person1.field(person::id); - let unread_filter = comment_reply::read - .eq(false) - .or(person_comment_mention::read.eq(false)) - .or(person_post_mention::read.eq(false)) - // If its unread, I only want the messages to me - .or( - private_message::read - .eq(false) - .and(private_message::recipient_id.eq(my_person_id)), - ); - let item_creator_join = comment::creator_id .eq(item_creator) .or( @@ -87,11 +69,13 @@ impl InboxCombinedViewInternal { ) .or(private_message::creator_id.eq(item_creator)); - let recipient_join = comment_reply::recipient_id - .eq(recipient_person) - .or(person_comment_mention::recipient_id.eq(recipient_person)) - .or(person_post_mention::recipient_id.eq(recipient_person)) - .or(private_message::recipient_id.eq(recipient_person)); + let recipient_join = aliases::person1.on( + comment_reply::recipient_id + .eq(recipient_person) + .or(person_comment_mention::recipient_id.eq(recipient_person)) + .or(person_post_mention::recipient_id.eq(recipient_person)) + .or(private_message::recipient_id.eq(recipient_person)), + ); let comment_join = comment_reply::comment_id .eq(comment::id) @@ -112,27 +96,107 @@ impl InboxCombinedViewInternal { .eq(private_message::id.nullable()) .and(not(private_message::deleted)); - let mut query = inbox_combined::table + let community_join = post::community_id.eq(community::id); + + let local_user_join = local_user::table.on( + item_creator + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ); + + let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id)); + let comment_aggregates_join = + comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)); + + let image_details_join = + image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())); + + let creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(item_creator), + ), + ); + + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(post::community_id) + .and(community_actions::person_id.eq(my_person_id)), + ); + + let instance_actions_join = instance_actions::table.on( + instance_actions::instance_id + .eq(person::instance_id) + .and(instance_actions::person_id.eq(my_person_id)), + ); + + let post_actions_join = post_actions::table.on( + post_actions::post_id + .eq(post::id) + .and(post_actions::person_id.eq(my_person_id)), + ); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(item_creator) + .and(person_actions::person_id.eq(my_person_id)), + ); + + let comment_actions_join = comment_actions::table.on( + comment_actions::comment_id + .eq(comment::id) + .and(comment_actions::person_id.eq(my_person_id)), + ); + + inbox_combined::table .left_join(comment_reply::table) .left_join(person_comment_mention::table) .left_join(person_post_mention::table) .left_join(private_message::table.on(private_message_join)) .left_join(comment::table.on(comment_join)) .left_join(post::table.on(post_join)) - // The item creator + .left_join(community::table.on(community_join)) .inner_join(person::table.on(item_creator_join)) - // The recipient - .inner_join(aliases::person1.on(recipient_join)) - .left_join(actions( - instance_actions::table, - Some(my_person_id), - person::instance_id, - )) - .left_join(actions( - person_actions::table, - Some(my_person_id), - item_creator, - )) + .inner_join(recipient_join) + .left_join(image_details_join) + .left_join(post_aggregates_join) + .left_join(comment_aggregates_join) + .left_join(creator_community_actions_join) + .left_join(local_user_join) + .left_join(community_actions_join) + .left_join(instance_actions_join) + .left_join(post_actions_join) + .left_join(person_actions_join) + .left_join(comment_actions_join) + } + + /// Gets the number of unread mentions + pub async fn get_unread_count( + pool: &mut DbPool<'_>, + my_person_id: PersonId, + show_bot_accounts: bool, + ) -> Result { + use diesel::dsl::count; + let conn = &mut get_conn(pool).await?; + + let recipient_person = aliases::person1.field(person::id); + + let unread_filter = comment_reply::read + .eq(false) + .or(person_comment_mention::read.eq(false)) + .or(person_post_mention::read.eq(false)) + // If its unread, I only want the messages to me + .or( + private_message::read + .eq(false) + .and(private_message::recipient_id.eq(my_person_id)), + ); + + let mut query = Self::joins(my_person_id) // Filter for your user .filter(recipient_person.eq(my_person_id)) // Filter unreads @@ -140,6 +204,7 @@ impl InboxCombinedViewInternal { // Don't count replies from blocked users .filter(person_actions::blocked.is_null()) .filter(instance_actions::blocked.is_null()) + .select(count(inbox_combined::id)) .into_boxed(); // These filters need to be kept in sync with the filters in queries().list() @@ -147,10 +212,7 @@ impl InboxCombinedViewInternal { query = query.filter(not(person::bot_account)); } - query - .select(count(inbox_combined::id)) - .first::(conn) - .await + query.first::(conn).await } } @@ -210,42 +272,6 @@ impl InboxCombinedQuery { let item_creator = person::id; let recipient_person = aliases::person1.field(person::id); - let item_creator_join = comment::creator_id - .eq(item_creator) - .or( - inbox_combined::person_post_mention_id - .is_not_null() - .and(post::creator_id.eq(item_creator)), - ) - .or(private_message::creator_id.eq(item_creator)); - - let recipient_join = comment_reply::recipient_id - .eq(recipient_person) - .or(person_comment_mention::recipient_id.eq(recipient_person)) - .or(person_post_mention::recipient_id.eq(recipient_person)) - .or(private_message::recipient_id.eq(recipient_person)); - - let comment_join = comment_reply::comment_id - .eq(comment::id) - .or(person_comment_mention::comment_id.eq(comment::id)) - // Filter out the deleted / removed - .and(not(comment::deleted)) - .and(not(comment::removed)); - - let post_join = person_post_mention::post_id - .eq(post::id) - .or(comment::post_id.eq(post::id)) - // Filter out the deleted / removed - .and(not(post::deleted)) - .and(not(post::removed)); - - // This could be a simple join, but you need to check for deleted here - let private_message_join = inbox_combined::private_message_id - .eq(private_message::id.nullable()) - .and(not(private_message::deleted)); - - let community_join = post::community_id.eq(community::id); - let post_tags = post_tag::table .inner_join(tag::table) .select(diesel::dsl::sql::( @@ -255,54 +281,7 @@ impl InboxCombinedQuery { .filter(tag::deleted.eq(false)) .single_value(); - let mut query = inbox_combined::table - .left_join(comment_reply::table) - .left_join(person_comment_mention::table) - .left_join(person_post_mention::table) - .left_join(private_message::table.on(private_message_join)) - .left_join(comment::table.on(comment_join)) - .left_join(post::table.on(post_join)) - .left_join(community::table.on(community_join)) - // The item creator - .inner_join(person::table.on(item_creator_join)) - // The recipient - .inner_join(aliases::person1.on(recipient_join)) - .left_join(actions_alias( - creator_community_actions, - item_creator, - post::community_id, - )) - .left_join( - local_user::table.on( - item_creator - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ) - .left_join(actions( - community_actions::table, - Some(my_person_id), - post::community_id, - )) - .left_join(actions( - instance_actions::table, - Some(my_person_id), - person::instance_id, - )) - .left_join(actions(post_actions::table, Some(my_person_id), post::id)) - .left_join(actions( - person_actions::table, - Some(my_person_id), - item_creator, - )) - .left_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id))) - .left_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) - .left_join(actions( - comment_actions::table, - Some(my_person_id), - comment::id, - )) - .left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()))) + let mut query = InboxCombinedViewInternal::joins(my_person_id) .select(( // Specific comment_reply::all_columns.nullable(), diff --git a/crates/db_views/src/combined/modlog_combined_view.rs b/crates/db_views/src/combined/modlog_combined_view.rs index a492be07c..af2b655cc 100644 --- a/crates/db_views/src/combined/modlog_combined_view.rs +++ b/crates/db_views/src/combined/modlog_combined_view.rs @@ -66,6 +66,146 @@ use lemmy_db_schema::{ ModlogActionType, }; use lemmy_utils::error::LemmyResult; +impl ModlogCombinedViewInternal { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(mod_person_id: Option, hide_modlog_names: Option) -> _ { + // The modded / other person + let other_person = aliases::person1.field(person::id); + + let show_mod_names: bool = !(hide_modlog_names.unwrap_or_default()); + let show_mod_names_expr = show_mod_names.into_sql::(); + + // The query for the admin / mod person + // It needs an OR condition to every mod table + // After this you can use person::id to refer to the moderator + let moderator_names_join = show_mod_names_expr + .or(person::id.nullable().eq(mod_person_id)) + .and( + admin_allow_instance::admin_person_id + .eq(person::id) + .or(admin_block_instance::admin_person_id.eq(person::id)) + .or(admin_purge_comment::admin_person_id.eq(person::id)) + .or(admin_purge_community::admin_person_id.eq(person::id)) + .or(admin_purge_person::admin_person_id.eq(person::id)) + .or(admin_purge_post::admin_person_id.eq(person::id)) + .or(mod_add::mod_person_id.eq(person::id)) + .or(mod_add_community::mod_person_id.eq(person::id)) + .or(mod_ban::mod_person_id.eq(person::id)) + .or(mod_ban_from_community::mod_person_id.eq(person::id)) + .or(mod_feature_post::mod_person_id.eq(person::id)) + .or(mod_hide_community::mod_person_id.eq(person::id)) + .or(mod_lock_post::mod_person_id.eq(person::id)) + .or(mod_remove_comment::mod_person_id.eq(person::id)) + .or(mod_remove_community::mod_person_id.eq(person::id)) + .or(mod_remove_post::mod_person_id.eq(person::id)) + .or(mod_transfer_community::mod_person_id.eq(person::id)), + ); + + let other_person_join = mod_add::other_person_id + .eq(other_person) + .or(mod_add_community::other_person_id.eq(other_person)) + .or(mod_ban::other_person_id.eq(other_person)) + .or(mod_ban_from_community::other_person_id.eq(other_person)) + // Some tables don't have the other_person_id directly, so you need to join + .or( + mod_feature_post::id + .is_not_null() + .and(post::creator_id.eq(other_person)), + ) + .or( + mod_lock_post::id + .is_not_null() + .and(post::creator_id.eq(other_person)), + ) + .or( + mod_remove_comment::id + .is_not_null() + .and(comment::creator_id.eq(other_person)), + ) + .or( + mod_remove_post::id + .is_not_null() + .and(post::creator_id.eq(other_person)), + ) + .or(mod_transfer_community::other_person_id.eq(other_person)); + + let comment_join = mod_remove_comment::comment_id.eq(comment::id); + + let post_join = admin_purge_comment::post_id + .eq(post::id) + .or(mod_feature_post::post_id.eq(post::id)) + .or(mod_lock_post::post_id.eq(post::id)) + .or( + mod_remove_comment::id + .is_not_null() + .and(comment::post_id.eq(post::id)), + ) + .or(mod_remove_post::post_id.eq(post::id)); + + let community_join = admin_purge_post::community_id + .eq(community::id) + .or(mod_add_community::community_id.eq(community::id)) + .or(mod_ban_from_community::community_id.eq(community::id)) + .or( + mod_feature_post::id + .is_not_null() + .and(post::community_id.eq(community::id)), + ) + .or(mod_hide_community::community_id.eq(community::id)) + .or( + mod_lock_post::id + .is_not_null() + .and(post::community_id.eq(community::id)), + ) + .or( + mod_remove_comment::id + .is_not_null() + .and(post::community_id.eq(community::id)), + ) + .or(mod_remove_community::community_id.eq(community::id)) + .or( + mod_remove_post::id + .is_not_null() + .and(post::community_id.eq(community::id)), + ) + .or(mod_transfer_community::community_id.eq(community::id)); + + let instance_join = admin_allow_instance::instance_id + .eq(instance::id) + .or(admin_block_instance::instance_id.eq(instance::id)); + + modlog_combined::table + .left_join(admin_allow_instance::table) + .left_join(admin_block_instance::table) + .left_join(admin_purge_comment::table) + .left_join(admin_purge_community::table) + .left_join(admin_purge_person::table) + .left_join(admin_purge_post::table) + .left_join(mod_add::table) + .left_join(mod_add_community::table) + .left_join(mod_ban::table) + .left_join(mod_ban_from_community::table) + .left_join(mod_feature_post::table) + .left_join(mod_hide_community::table) + .left_join(mod_lock_post::table) + .left_join(mod_remove_comment::table) + .left_join(mod_remove_community::table) + .left_join(mod_remove_post::table) + .left_join(mod_transfer_community::table) + // The moderator + .left_join(person::table.on(moderator_names_join)) + // The comment + .left_join(comment::table.on(comment_join)) + // The post + .left_join(post::table.on(post_join)) + // The community + .left_join(community::table.on(community_join)) + // The instance + .left_join(instance::table.on(instance_join)) + // The other / modded person + .left_join(aliases::person1.on(other_person_join)) + } +} impl ModlogCombinedPaginationCursor { // get cursor for page that starts immediately after the given post @@ -159,167 +299,10 @@ pub struct ModlogCombinedQuery { impl ModlogCombinedQuery { pub async fn list(self, pool: &mut DbPool<'_>) -> LemmyResult> { let conn = &mut get_conn(pool).await?; - - let mod_person = self.mod_person_id.unwrap_or(PersonId(-1)); - let show_mod_names = !(self.hide_modlog_names.unwrap_or_default()); - let show_mod_names_expr = show_mod_names.as_sql::(); - - // The modded / other person let other_person = aliases::person1.field(person::id); - // The query for the admin / mod person - // It needs an OR condition to every mod table - // After this you can use person::id to refer to the moderator - let moderator_names_join = show_mod_names_expr.or(person::id.eq(mod_person)).and( - admin_allow_instance::admin_person_id - .eq(person::id) - .or(admin_block_instance::admin_person_id.eq(person::id)) - .or(admin_purge_comment::admin_person_id.eq(person::id)) - .or(admin_purge_community::admin_person_id.eq(person::id)) - .or(admin_purge_person::admin_person_id.eq(person::id)) - .or(admin_purge_post::admin_person_id.eq(person::id)) - .or(mod_add::mod_person_id.eq(person::id)) - .or(mod_add_community::mod_person_id.eq(person::id)) - .or(mod_ban::mod_person_id.eq(person::id)) - .or(mod_ban_from_community::mod_person_id.eq(person::id)) - .or(mod_feature_post::mod_person_id.eq(person::id)) - .or(mod_hide_community::mod_person_id.eq(person::id)) - .or(mod_lock_post::mod_person_id.eq(person::id)) - .or(mod_remove_comment::mod_person_id.eq(person::id)) - .or(mod_remove_community::mod_person_id.eq(person::id)) - .or(mod_remove_post::mod_person_id.eq(person::id)) - .or(mod_transfer_community::mod_person_id.eq(person::id)), - ); - - let other_person_join = mod_add::other_person_id - .eq(other_person) - .or(mod_add_community::other_person_id.eq(other_person)) - .or(mod_ban::other_person_id.eq(other_person)) - .or(mod_ban_from_community::other_person_id.eq(other_person)) - // Some tables don't have the other_person_id directly, so you need to join - .or( - mod_feature_post::id - .is_not_null() - .and(post::creator_id.eq(other_person)), - ) - .or( - mod_lock_post::id - .is_not_null() - .and(post::creator_id.eq(other_person)), - ) - .or( - mod_remove_comment::id - .is_not_null() - .and(comment::creator_id.eq(other_person)), - ) - .or( - mod_remove_post::id - .is_not_null() - .and(post::creator_id.eq(other_person)), - ) - .or(mod_transfer_community::other_person_id.eq(other_person)); - - let comment_join = mod_remove_comment::comment_id.eq(comment::id); - - let post_join = admin_purge_comment::post_id - .eq(post::id) - .or(mod_feature_post::post_id.eq(post::id)) - .or(mod_lock_post::post_id.eq(post::id)) - .or( - mod_remove_comment::id - .is_not_null() - .and(comment::post_id.eq(post::id)), - ) - .or(mod_remove_post::post_id.eq(post::id)); - - let community_join = admin_purge_post::community_id - .eq(community::id) - .or(mod_add_community::community_id.eq(community::id)) - .or(mod_ban_from_community::community_id.eq(community::id)) - .or( - mod_feature_post::id - .is_not_null() - .and(post::community_id.eq(community::id)), - ) - .or(mod_hide_community::community_id.eq(community::id)) - .or( - mod_lock_post::id - .is_not_null() - .and(post::community_id.eq(community::id)), - ) - .or( - mod_remove_comment::id - .is_not_null() - .and(post::community_id.eq(community::id)), - ) - .or(mod_remove_community::community_id.eq(community::id)) - .or( - mod_remove_post::id - .is_not_null() - .and(post::community_id.eq(community::id)), - ) - .or(mod_transfer_community::community_id.eq(community::id)); - - let instance_join = admin_allow_instance::instance_id - .eq(instance::id) - .or(admin_block_instance::instance_id.eq(instance::id)); - - let mut query = modlog_combined::table - .left_join(admin_allow_instance::table) - .left_join(admin_block_instance::table) - .left_join(admin_purge_comment::table) - .left_join(admin_purge_community::table) - .left_join(admin_purge_person::table) - .left_join(admin_purge_post::table) - .left_join(mod_add::table) - .left_join(mod_add_community::table) - .left_join(mod_ban::table) - .left_join(mod_ban_from_community::table) - .left_join(mod_feature_post::table) - .left_join(mod_hide_community::table) - .left_join(mod_lock_post::table) - .left_join(mod_remove_comment::table) - .left_join(mod_remove_community::table) - .left_join(mod_remove_post::table) - .left_join(mod_transfer_community::table) - // The moderator - .left_join(person::table.on(moderator_names_join)) - // The comment - .left_join(comment::table.on(comment_join)) - // The post - .left_join(post::table.on(post_join)) - // The community - .left_join(community::table.on(community_join)) - // The instance - .left_join(instance::table.on(instance_join)) - // The other / modded person - .left_join(aliases::person1.on(other_person_join)) - .select(( - admin_allow_instance::all_columns.nullable(), - admin_block_instance::all_columns.nullable(), - admin_purge_comment::all_columns.nullable(), - admin_purge_community::all_columns.nullable(), - admin_purge_person::all_columns.nullable(), - admin_purge_post::all_columns.nullable(), - mod_add::all_columns.nullable(), - mod_add_community::all_columns.nullable(), - mod_ban::all_columns.nullable(), - mod_ban_from_community::all_columns.nullable(), - mod_feature_post::all_columns.nullable(), - mod_hide_community::all_columns.nullable(), - mod_lock_post::all_columns.nullable(), - mod_remove_comment::all_columns.nullable(), - mod_remove_community::all_columns.nullable(), - mod_remove_post::all_columns.nullable(), - mod_transfer_community::all_columns.nullable(), - // Shared - person::all_columns.nullable(), - aliases::person1.fields(person::all_columns).nullable(), - instance::all_columns.nullable(), - community::all_columns.nullable(), - post::all_columns.nullable(), - comment::all_columns.nullable(), - )) + let mut query = ModlogCombinedViewInternal::joins(self.mod_person_id, self.hide_modlog_names) + .select(ModlogCombinedViewInternal::as_select()) .into_boxed(); if let Some(mod_person_id) = self.mod_person_id { diff --git a/crates/db_views/src/combined/person_content_combined_view.rs b/crates/db_views/src/combined/person_content_combined_view.rs index 65b4d43e6..898ece8b7 100644 --- a/crates/db_views/src/combined/person_content_combined_view.rs +++ b/crates/db_views/src/combined/person_content_combined_view.rs @@ -31,6 +31,7 @@ use lemmy_db_schema::{ person, person_actions, person_content_combined, + person_saved_combined, post, post_actions, post_aggregates, @@ -42,11 +43,197 @@ use lemmy_db_schema::{ community::CommunityFollower, }, traits::InternalToCombinedView, - utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool}, + utils::{functions::coalesce, get_conn, DbPool}, PersonContentType, }; use lemmy_utils::error::LemmyResult; +impl PersonContentCombinedViewInternal { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(my_person_id: Option) -> _ { + let item_creator = person::id; + + let comment_join = + comment::table.on(person_content_combined::comment_id.eq(comment::id.nullable())); + + let post_join = post::table.on( + person_content_combined::post_id + .eq(post::id.nullable()) + .or(comment::post_id.eq(post::id)), + ); + + let item_creator_join = person::table.on( + comment::creator_id + .eq(item_creator) + // Need to filter out the post rows where the post_id given is null + // Otherwise you'll get duped post rows + .or( + post::creator_id + .eq(item_creator) + .and(person_content_combined::post_id.is_not_null()), + ), + ); + + let community_join = community::table.on(post::community_id.eq(community::id)); + + let creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(item_creator), + ), + ); + + let local_user_join = local_user::table.on( + item_creator + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ); + + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(post::community_id) + .and(community_actions::person_id.nullable().eq(my_person_id)), + ); + + let post_actions_join = post_actions::table.on( + post_actions::post_id + .eq(post::id) + .and(post_actions::person_id.nullable().eq(my_person_id)), + ); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(item_creator) + .and(person_actions::person_id.nullable().eq(my_person_id)), + ); + + let comment_actions_join = comment_actions::table.on( + comment_actions::comment_id + .eq(comment::id) + .and(comment_actions::person_id.nullable().eq(my_person_id)), + ); + + let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id)); + + let comment_aggregates_join = comment_aggregates::table + .on(person_content_combined::comment_id.eq(comment_aggregates::comment_id.nullable())); + + let image_details_join = + image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())); + + person_content_combined::table + .left_join(comment_join) + .inner_join(post_join) + .inner_join(item_creator_join) + .inner_join(community_join) + .left_join(creator_community_actions_join) + .left_join(local_user_join) + .left_join(community_actions_join) + .left_join(post_actions_join) + .left_join(person_actions_join) + .inner_join(post_aggregates_join) + .left_join(comment_aggregates_join) + .left_join(comment_actions_join) + .left_join(image_details_join) + } + + #[diesel::dsl::auto_type(no_type_alias)] + pub(crate) fn joins_saved(my_person_id: PersonId) -> _ { + let item_creator = person::id; + + let comment_join = + comment::table.on(person_saved_combined::comment_id.eq(comment::id.nullable())); + + let post_join = post::table.on( + person_saved_combined::post_id + .eq(post::id.nullable()) + .or(comment::post_id.eq(post::id)), + ); + + let item_creator_join = person::table.on( + comment::creator_id + .eq(item_creator) + // Need to filter out the post rows where the post_id given is null + // Otherwise you'll get duped post rows + .or( + post::creator_id + .eq(item_creator) + .and(person_saved_combined::post_id.is_not_null()), + ), + ); + + let community_join = community::table.on(post::community_id.eq(community::id)); + + let creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(item_creator), + ), + ); + + let local_user_join = local_user::table.on( + item_creator + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ); + + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(post::community_id) + .and(community_actions::person_id.eq(my_person_id)), + ); + + let post_actions_join = post_actions::table.on( + post_actions::post_id + .eq(post::id) + .and(post_actions::person_id.eq(my_person_id)), + ); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(item_creator) + .and(person_actions::person_id.eq(my_person_id)), + ); + + let comment_actions_join = comment_actions::table.on( + comment_actions::comment_id + .eq(comment::id) + .and(comment_actions::person_id.eq(my_person_id)), + ); + + let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id)); + + let comment_aggregates_join = comment_aggregates::table + .on(person_saved_combined::comment_id.eq(comment_aggregates::comment_id.nullable())); + + let image_details_join = + image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())); + + person_saved_combined::table + .left_join(comment_join) + .inner_join(post_join) + .inner_join(item_creator_join) + .inner_join(community_join) + .left_join(creator_community_actions_join) + .left_join(local_user_join) + .left_join(community_actions_join) + .left_join(post_actions_join) + .left_join(person_actions_join) + .inner_join(post_aggregates_join) + .left_join(comment_aggregates_join) + .left_join(comment_actions_join) + .left_join(image_details_join) + } +} + impl PersonContentCombinedPaginationCursor { // get cursor for page that starts immediately after the given post pub fn after_post(view: &PersonContentCombinedView) -> PersonContentCombinedPaginationCursor { @@ -115,61 +302,7 @@ impl PersonContentCombinedQuery { // For example, the creator must be the person table joined to either: // - post.creator_id // - comment.creator_id - let query = person_content_combined::table - // The comment - .left_join(comment::table.on(person_content_combined::comment_id.eq(comment::id.nullable()))) - // The post - // It gets a bit complicated here, because since both comments and post combined have a post - // attached, you can do an inner join. - .inner_join( - post::table.on( - person_content_combined::post_id - .eq(post::id.nullable()) - .or(comment::post_id.eq(post::id)), - ), - ) - // The item creator - .inner_join( - person::table.on( - comment::creator_id - .eq(item_creator) - // Need to filter out the post rows where the post_id given is null - // Otherwise you'll get duped post rows - .or( - post::creator_id - .eq(item_creator) - .and(person_content_combined::post_id.is_not_null()), - ), - ), - ) - // The community - .inner_join(community::table.on(post::community_id.eq(community::id))) - .left_join(actions_alias( - creator_community_actions, - item_creator, - post::community_id, - )) - .left_join( - local_user::table.on( - item_creator - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ) - .left_join(actions( - community_actions::table, - my_person_id, - post::community_id, - )) - .left_join(actions(post_actions::table, my_person_id, post::id)) - .left_join(actions(person_actions::table, my_person_id, item_creator)) - .inner_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id))) - .left_join( - comment_aggregates::table - .on(person_content_combined::comment_id.eq(comment_aggregates::comment_id.nullable())), - ) - .left_join(actions(comment_actions::table, my_person_id, comment::id)) - .left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()))) + let mut query = PersonContentCombinedViewInternal::joins(my_person_id) // The creator id filter .filter(item_creator.eq(self.creator_id)) .select(( @@ -209,8 +342,6 @@ impl PersonContentCombinedQuery { )) .into_boxed(); - let mut query = PaginatedQueryBuilder::new(query); - if let Some(type_) = self.type_ { query = match type_ { PersonContentType::All => query, @@ -221,6 +352,8 @@ impl PersonContentCombinedQuery { } } + let mut query = PaginatedQueryBuilder::new(query); + let page_after = self.page_after.map(|c| c.0); if self.page_back.unwrap_or_default() { diff --git a/crates/db_views/src/combined/person_saved_combined_view.rs b/crates/db_views/src/combined/person_saved_combined_view.rs index 4ac7ee3fe..326c5de72 100644 --- a/crates/db_views/src/combined/person_saved_combined_view.rs +++ b/crates/db_views/src/combined/person_saved_combined_view.rs @@ -6,9 +6,7 @@ use crate::structs::{ }; use diesel::{ result::Error, - BoolExpressionMethods, ExpressionMethods, - JoinOnDsl, NullableExpressionMethods, QueryDsl, SelectableHelper, @@ -39,7 +37,7 @@ use lemmy_db_schema::{ community::CommunityFollower, }, traits::InternalToCombinedView, - utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool}, + utils::{functions::coalesce, get_conn, DbPool}, PersonContentType, }; use lemmy_utils::error::LemmyResult; @@ -90,7 +88,6 @@ impl PersonSavedCombinedQuery { user: &LocalUserView, ) -> LemmyResult> { let my_person_id = user.local_user.person_id; - let item_creator = person::id; let conn = &mut get_conn(pool).await?; @@ -103,75 +100,7 @@ impl PersonSavedCombinedQuery { .filter(tag::deleted.eq(false)) .single_value(); - // Notes: since the post_id and comment_id are optional columns, - // many joins must use an OR condition. - // For example, the creator must be the person table joined to either: - // - post.creator_id - // - comment.creator_id - let query = person_saved_combined::table - // The comment - .left_join(comment::table.on(person_saved_combined::comment_id.eq(comment::id.nullable()))) - // The post - // It gets a bit complicated here, because since both comments and post combined have a post - // attached, you can do an inner join. - .inner_join( - post::table.on( - person_saved_combined::post_id - .eq(post::id.nullable()) - .or(comment::post_id.eq(post::id)), - ), - ) - // The item creator - .inner_join( - person::table.on( - comment::creator_id - .eq(item_creator) - // Need to filter out the post rows where the post_id given is null - // Otherwise you'll get duped post rows - .or( - post::creator_id - .eq(item_creator) - .and(person_saved_combined::post_id.is_not_null()), - ), - ), - ) - // The community - .inner_join(community::table.on(post::community_id.eq(community::id))) - .left_join(actions_alias( - creator_community_actions, - item_creator, - post::community_id, - )) - .left_join( - local_user::table.on( - item_creator - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ) - .left_join(actions( - community_actions::table, - Some(my_person_id), - post::community_id, - )) - .left_join(actions(post_actions::table, Some(my_person_id), post::id)) - .left_join(actions( - person_actions::table, - Some(my_person_id), - item_creator, - )) - .inner_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id))) - .left_join( - comment_aggregates::table - .on(person_saved_combined::comment_id.eq(comment_aggregates::comment_id.nullable())), - ) - .left_join(actions( - comment_actions::table, - Some(my_person_id), - comment::id, - )) - .left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()))) - // The person id filter + let mut query = PersonContentCombinedViewInternal::joins_saved(my_person_id) .filter(person_saved_combined::person_id.eq(my_person_id)) .select(( // Post-specific @@ -210,8 +139,6 @@ impl PersonSavedCombinedQuery { )) .into_boxed(); - let mut query = PaginatedQueryBuilder::new(query); - if let Some(type_) = self.type_ { query = match type_ { PersonContentType::All => query, @@ -222,6 +149,8 @@ impl PersonSavedCombinedQuery { } } + let mut query = PaginatedQueryBuilder::new(query); + let page_after = self.page_after.map(|c| c.0); if self.page_back.unwrap_or_default() { diff --git a/crates/db_views/src/combined/report_combined_view.rs b/crates/db_views/src/combined/report_combined_view.rs index 0fce1afcb..115c8413a 100644 --- a/crates/db_views/src/combined/report_combined_view.rs +++ b/crates/db_views/src/combined/report_combined_view.rs @@ -22,7 +22,7 @@ use diesel_async::RunQueryDsl; use i_love_jesus::PaginatedQueryBuilder; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, - newtypes::{CommunityId, PostId}, + newtypes::{CommunityId, PersonId, PostId}, schema::{ comment, comment_actions, @@ -48,12 +48,130 @@ use lemmy_db_schema::{ community::CommunityFollower, }, traits::InternalToCombinedView, - utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool, ReverseTimestampKey}, + utils::{functions::coalesce, get_conn, DbPool, ReverseTimestampKey}, ReportType, }; use lemmy_utils::error::LemmyResult; impl ReportCombinedViewInternal { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(my_person_id: PersonId) -> _ { + let report_creator = person::id; + let item_creator = aliases::person1.field(person::id); + let resolver = aliases::person2.field(person::id).nullable(); + + let comment_join = comment::table.on(comment_report::comment_id.eq(comment::id)); + let private_message_join = + private_message::table.on(private_message_report::private_message_id.eq(private_message::id)); + + let post_join = post::table.on( + post_report::post_id + .eq(post::id) + .or(comment::post_id.eq(post::id)), + ); + + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(community::id) + .and(community_actions::person_id.eq(my_person_id)), + ); + + let report_creator_join = person::table.on( + post_report::creator_id + .eq(report_creator) + .or(comment_report::creator_id.eq(report_creator)) + .or(private_message_report::creator_id.eq(report_creator)) + .or(community_report::creator_id.eq(report_creator)), + ); + + let item_creator_join = aliases::person1.on( + post::creator_id + .eq(item_creator) + .or(comment::creator_id.eq(item_creator)) + .or(private_message::creator_id.eq(item_creator)), + ); + + let resolver_join = aliases::person2.on( + private_message_report::resolver_id + .eq(resolver) + .or(post_report::resolver_id.eq(resolver)) + .or(comment_report::resolver_id.eq(resolver)) + .or(community_report::resolver_id.eq(resolver)), + ); + + let community_join = community::table.on( + community_report::community_id + .eq(community::id) + .or(post::community_id.eq(community::id)), + ); + + let local_user_join = local_user::table.on( + item_creator + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ); + + let creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(item_creator), + ), + ); + + let post_actions_join = post_actions::table.on( + post_actions::post_id + .eq(post::id) + .and(post_actions::person_id.eq(my_person_id)), + ); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(item_creator) + .and(person_actions::person_id.eq(my_person_id)), + ); + + let comment_actions_join = comment_actions::table.on( + comment_actions::comment_id + .eq(comment::id) + .and(comment_actions::person_id.eq(my_person_id)), + ); + + let post_aggregates_join = + post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)); + + let comment_aggregates_join = + comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)); + + let community_aggregates_join = community_aggregates::table + .on(community_report::community_id.eq(community_aggregates::community_id)); + + report_combined::table + .left_join(post_report::table) + .left_join(comment_report::table) + .left_join(private_message_report::table) + .left_join(community_report::table) + .inner_join(report_creator_join) + .left_join(comment_join) + .left_join(private_message_join) + .left_join(post_join) + .left_join(item_creator_join) + .left_join(resolver_join) + .left_join(community_join) + .left_join(creator_community_actions_join) + .left_join(local_user_join) + .left_join(community_actions_join) + .left_join(post_actions_join) + .left_join(person_actions_join) + .left_join(post_aggregates_join) + .left_join(comment_aggregates_join) + .left_join(community_aggregates_join) + .left_join(comment_actions_join) + } + /// returns the current unresolved report count for the communities you mod pub async fn get_report_count( pool: &mut DbPool<'_>, @@ -65,27 +183,7 @@ impl ReportCombinedViewInternal { let conn = &mut get_conn(pool).await?; let my_person_id = user.local_user.person_id; - let mut query = report_combined::table - .left_join(post_report::table) - .left_join(comment_report::table) - .left_join(private_message_report::table) - .left_join(community_report::table) - // Need to join to comment and post to get the community - .left_join(comment::table.on(comment_report::comment_id.eq(comment::id))) - // The post - .left_join( - post::table.on( - post_report::post_id - .eq(post::id) - .or(comment::post_id.eq(post::id)), - ), - ) - .left_join(community::table.on(post::community_id.eq(community::id))) - .left_join(actions( - community_actions::table, - Some(my_person_id), - post::community_id, - )) + let mut query = Self::joins(my_person_id) .filter( post_report::resolved .or(comment_report::resolved) @@ -93,21 +191,27 @@ impl ReportCombinedViewInternal { .or(community_report::resolved) .is_distinct_from(true), ) + .select(count(report_combined::id)) .into_boxed(); if let Some(community_id) = community_id { - query = query.filter(post::community_id.eq(community_id)) + query = query.filter( + community::id + .eq(community_id) + .and(report_combined::community_report_id.is_null()), + ); } // If its not an admin, get only the ones you mod if !user.local_user.admin { - query = query.filter(community_actions::became_moderator.is_not_null()); + query = query.filter( + community_actions::became_moderator + .is_not_null() + .and(report_combined::community_report_id.is_null()), + ); } - query - .select(count(report_combined::id)) - .first::(conn) - .await + query.first::(conn).await } } @@ -164,102 +268,9 @@ impl ReportCombinedQuery { user: &LocalUserView, ) -> LemmyResult> { let my_person_id = user.local_user.person_id; - let report_creator = person::id; - let item_creator = aliases::person1.field(person::id); - let resolver = aliases::person2.field(person::id).nullable(); let conn = &mut get_conn(pool).await?; - - let report_creator_join = post_report::creator_id - .eq(report_creator) - .or(comment_report::creator_id.eq(report_creator)) - .or(private_message_report::creator_id.eq(report_creator)) - .or(community_report::creator_id.eq(report_creator)); - - let item_creator_join = post::creator_id - .eq(item_creator) - .or(comment::creator_id.eq(item_creator)) - .or(private_message::creator_id.eq(item_creator)); - - let resolver_join = private_message_report::resolver_id - .eq(resolver) - .or(post_report::resolver_id.eq(resolver)) - .or(comment_report::resolver_id.eq(resolver)) - .or(community_report::resolver_id.eq(resolver)); - - let post_join = post_report::post_id - .eq(post::id) - .or(comment::post_id.eq(post::id)); - - let community_join = community::table.on( - community_report::community_id - .eq(community::id) - .or(post::community_id.eq(community::id)), - ); - - // Notes: since the post_report_id and comment_report_id are optional columns, - // many joins must use an OR condition. - // For example, the report creator must be the person table joined to either: - // - post_report.creator_id - // - comment_report.creator_id - let mut query = report_combined::table - .left_join(post_report::table) - .left_join(comment_report::table) - .left_join(private_message_report::table) - .left_join(community_report::table) - // The report creator - .inner_join(person::table.on(report_creator_join)) - // The comment - .left_join(comment::table.on(comment_report::comment_id.eq(comment::id))) - // The private message - .left_join( - private_message::table - .on(private_message_report::private_message_id.eq(private_message::id)), - ) - // The post - .left_join(post::table.on(post_join)) - // The item creator (`item_creator` is the id of this person) - .left_join(aliases::person1.on(item_creator_join)) - // The resolver - .left_join(aliases::person2.on(resolver_join)) - // The community - .left_join(community_join) - .left_join(actions_alias( - creator_community_actions, - item_creator, - post::community_id, - )) - .left_join( - local_user::table.on( - item_creator - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ) - .left_join(actions( - community_actions::table, - Some(my_person_id), - community::id, - )) - .left_join(actions(post_actions::table, Some(my_person_id), post::id)) - .left_join(actions( - person_actions::table, - Some(my_person_id), - item_creator, - )) - .left_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) - .left_join( - comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), - ) - .left_join( - community_aggregates::table - .on(community_report::community_id.eq(community_aggregates::community_id)), - ) - .left_join(actions( - comment_actions::table, - Some(my_person_id), - comment_report::comment_id, - )) + let mut query = ReportCombinedViewInternal::joins(my_person_id) .select(( // Post-specific post_report::all_columns.nullable(), diff --git a/crates/db_views/src/combined/search_combined_view.rs b/crates/db_views/src/combined/search_combined_view.rs index 13054da0a..05c999ef1 100644 --- a/crates/db_views/src/combined/search_combined_view.rs +++ b/crates/db_views/src/combined/search_combined_view.rs @@ -48,15 +48,7 @@ use lemmy_db_schema::{ community::CommunityFollower, }, traits::InternalToCombinedView, - utils::{ - actions, - actions_alias, - functions::coalesce, - fuzzy_search, - get_conn, - DbPool, - ReverseTimestampKey, - }, + utils::{functions::coalesce, fuzzy_search, get_conn, DbPool, ReverseTimestampKey}, ListingType, SearchSortType, SearchType, @@ -64,6 +56,123 @@ use lemmy_db_schema::{ use lemmy_utils::error::LemmyResult; use SearchSortType::*; +impl SearchCombinedViewInternal { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(my_person_id: Option) -> _ { + let item_creator = person::id; + + let item_creator_join = person::table.on( + search_combined::person_id + .eq(item_creator.nullable()) + .or( + search_combined::comment_id + .is_not_null() + .and(comment::creator_id.eq(item_creator)), + ) + .or( + search_combined::post_id + .is_not_null() + .and(post::creator_id.eq(item_creator)), + ) + .and(not(person::deleted)), + ); + + let comment_join = comment::table.on( + search_combined::comment_id + .eq(comment::id.nullable()) + .and(not(comment::removed)) + .and(not(comment::deleted)), + ); + + let post_join = post::table.on( + search_combined::post_id + .eq(post::id.nullable()) + .or(comment::post_id.eq(post::id)) + .and(not(post::removed)) + .and(not(post::deleted)), + ); + + let community_join = community::table.on( + search_combined::community_id + .eq(community::id.nullable()) + .or(post::community_id.eq(community::id)) + .and(not(community::removed)) + .and(not(community::deleted)), + ); + + let creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(community::id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(item_creator), + ), + ); + let local_user_join = local_user::table.on( + item_creator + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ); + + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(community::id) + .and(community_actions::person_id.nullable().eq(my_person_id)), + ); + + let post_actions_join = post_actions::table.on( + post_actions::post_id + .eq(post::id) + .and(post_actions::person_id.nullable().eq(my_person_id)), + ); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(item_creator) + .and(person_actions::person_id.nullable().eq(my_person_id)), + ); + + let comment_actions_join = comment_actions::table.on( + comment_actions::comment_id + .eq(comment::id) + .and(comment_actions::person_id.nullable().eq(my_person_id)), + ); + + let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id)); + + let comment_aggregates_join = comment_aggregates::table + .on(search_combined::comment_id.eq(comment_aggregates::comment_id.nullable())); + + let community_aggregates_join = community_aggregates::table + .on(search_combined::community_id.eq(community_aggregates::community_id.nullable())); + + let image_details_join = + image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())); + + let person_aggregates_join = person_aggregates::table + .on(search_combined::person_id.eq(person_aggregates::person_id.nullable())); + + search_combined::table + .left_join(comment_join) + .left_join(post_join) + .left_join(item_creator_join) + .left_join(community_join) + .left_join(creator_community_actions_join) + .left_join(local_user_join) + .left_join(community_actions_join) + .left_join(post_actions_join) + .left_join(person_actions_join) + .left_join(person_aggregates_join) + .left_join(post_aggregates_join) + .left_join(comment_aggregates_join) + .left_join(community_aggregates_join) + .left_join(comment_actions_join) + .left_join(image_details_join) + } +} + impl SearchCombinedPaginationCursor { // get cursor for page that starts immediately after the given post pub fn after_post(view: &SearchCombinedView) -> SearchCombinedPaginationCursor { @@ -136,85 +245,7 @@ impl SearchCombinedQuery { .filter(tag::deleted.eq(false)) .single_value(); - let item_creator_join = search_combined::person_id - .eq(item_creator.nullable()) - .or( - search_combined::comment_id - .is_not_null() - .and(comment::creator_id.eq(item_creator)), - ) - .or( - search_combined::post_id - .is_not_null() - .and(post::creator_id.eq(item_creator)), - ) - .and(not(person::deleted)); - - let comment_join = search_combined::comment_id - .eq(comment::id.nullable()) - .and(not(comment::removed)) - .and(not(comment::deleted)); - - let post_join = search_combined::post_id - .eq(post::id.nullable()) - .or(comment::post_id.eq(post::id)) - .and(not(post::removed)) - .and(not(post::deleted)); - - let community_join = search_combined::community_id - .eq(community::id.nullable()) - .or(post::community_id.eq(community::id)) - .and(not(community::removed)) - .and(not(community::deleted)); - - // Notes: since the post_id and comment_id are optional columns, - // many joins must use an OR condition. - // For example, the creator must be the person table joined to either: - // - post.creator_id - // - comment.creator_id - let mut query = search_combined::table - // The comment - .left_join(comment::table.on(comment_join)) - // The post - .left_join(post::table.on(post_join)) - // The item creator - .left_join(person::table.on(item_creator_join)) - // The community - .left_join(community::table.on(community_join)) - .left_join(actions_alias( - creator_community_actions, - item_creator, - community::id, - )) - .left_join( - local_user::table.on( - item_creator - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ) - .left_join(actions( - community_actions::table, - my_person_id, - community::id, - )) - .left_join(actions(post_actions::table, my_person_id, post::id)) - .left_join(actions(person_actions::table, my_person_id, item_creator)) - .left_join( - person_aggregates::table - .on(search_combined::person_id.eq(person_aggregates::person_id.nullable())), - ) - .left_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id))) - .left_join( - comment_aggregates::table - .on(search_combined::comment_id.eq(comment_aggregates::comment_id.nullable())), - ) - .left_join( - community_aggregates::table - .on(search_combined::community_id.eq(community_aggregates::community_id.nullable())), - ) - .left_join(actions(comment_actions::table, my_person_id, comment::id)) - .left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()))) + let mut query = SearchCombinedViewInternal::joins(my_person_id) .select(( // Post-specific post::all_columns.nullable(), diff --git a/crates/db_views/src/comment/comment_view.rs b/crates/db_views/src/comment/comment_view.rs index 13a849449..9297eec60 100644 --- a/crates/db_views/src/comment/comment_view.rs +++ b/crates/db_views/src/comment/comment_view.rs @@ -1,7 +1,6 @@ use crate::structs::{CommentSlimView, CommentView}; use diesel::{ dsl::{exists, not}, - pg::Pg, result::Error, BoolExpressionMethods, ExpressionMethods, @@ -9,13 +8,14 @@ use diesel::{ NullableExpressionMethods, PgTextExpressionMethods, QueryDsl, + SelectableHelper, }; use diesel_async::RunQueryDsl; use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions}; use lemmy_db_schema::{ aliases::creator_community_actions, impls::local_user::LocalUserOptionHelper, - newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId}, + newtypes::{CommentId, CommunityId, PersonId, PostId}, schema::{ comment, comment_actions, @@ -23,108 +23,82 @@ use lemmy_db_schema::{ community, community_actions, instance_actions, - local_user, local_user_language, person, person_actions, post, }, - source::{ - community::{CommunityFollower, CommunityFollowerState}, - local_user::LocalUser, - site::Site, - }, - utils::{ - actions, - actions_alias, - fuzzy_search, - limit_and_offset, - DbConn, - DbPool, - ListFn, - Queries, - ReadFn, - }, + source::{community::CommunityFollowerState, local_user::LocalUser, site::Site}, + utils::{fuzzy_search, get_conn, limit_and_offset, DbPool}, CommentSortType, CommunityVisibility, ListingType, }; -type QueriesReadTypes<'a> = (CommentId, Option<&'a LocalUser>); -type QueriesListTypes<'a> = (CommentQuery<'a>, &'a Site); +impl CommentView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(my_person_id: Option) -> _ { + let community_join = community::table.on(post::community_id.eq(community::id)); -fn queries<'a>() -> Queries< - impl ReadFn<'a, CommentView, QueriesReadTypes<'a>>, - impl ListFn<'a, CommentView, QueriesListTypes<'a>>, -> { - let creator_is_admin = exists( - local_user::table.filter( - comment::creator_id - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ); + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(post::community_id) + .and(community_actions::person_id.nullable().eq(my_person_id)), + ); - let all_joins = move |query: comment::BoxedQuery<'a, Pg>, my_person_id: Option| { - query + let comment_actions_join = comment_actions::table.on( + comment_actions::comment_id + .eq(comment_aggregates::comment_id) + .and(comment_actions::person_id.nullable().eq(my_person_id)), + ); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(comment::creator_id) + .and(person_actions::person_id.nullable().eq(my_person_id)), + ); + + let instance_actions_join = instance_actions::table.on( + instance_actions::instance_id + .eq(community::instance_id) + .and(instance_actions::person_id.nullable().eq(my_person_id)), + ); + + let comment_creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(comment::creator_id), + ), + ); + + comment::table .inner_join(person::table) .inner_join(post::table) - .inner_join(community::table.on(post::community_id.eq(community::id))) + .inner_join(community_join) .inner_join(comment_aggregates::table) - .left_join(actions( - community_actions::table, - my_person_id, - post::community_id, - )) - .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(( - comment::all_columns, - person::all_columns, - post::all_columns, - community::all_columns, - comment_aggregates::all_columns, - creator_community_actions - .field(community_actions::received_ban) - .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, - CommunityFollower::select_subscribed_type(), - comment_actions::saved.nullable().is_not_null(), - person_actions::blocked.nullable().is_not_null(), - comment_actions::like_score.nullable(), - )) - }; + .left_join(community_actions_join) + .left_join(comment_actions_join) + .left_join(person_actions_join) + .left_join(instance_actions_join) + .left_join(comment_creator_community_actions_join) + } + + pub async fn read( + pool: &mut DbPool<'_>, + comment_id: CommentId, + my_local_user: Option<&'_ LocalUser>, + ) -> Result { + let conn = &mut get_conn(pool).await?; + + let mut query = Self::joins(my_local_user.person_id()) + .filter(comment::id.eq(comment_id)) + .select(Self::as_select()) + .into_boxed(); - let read = move |mut conn: DbConn<'a>, - (comment_id, my_local_user): (CommentId, Option<&'a LocalUser>)| async move { - let mut query = all_joins( - comment::table.find(comment_id).into_boxed(), - my_local_user.person_id(), - ); query = my_local_user.visible_communities_only(query); // Check permissions to view private community content. @@ -138,14 +112,65 @@ fn queries<'a>() -> Queries< .or(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), ); } - query.first(&mut conn).await - }; - let list = move |mut conn: DbConn<'a>, (o, site): (CommentQuery<'a>, &'a Site)| async move { + let mut res = query.first::(conn).await?; + + // If a person is given, then my_vote (res.9), if None, should be 0, not null + // Necessary to differentiate between other person's votes + if my_local_user.is_some() && res.my_vote.is_none() { + res.my_vote = Some(0); + } + + let is_admin = my_local_user.map(|u| u.admin).unwrap_or(false); + Ok(handle_deleted(res, is_admin)) + } + + pub fn map_to_slim(self) -> CommentSlimView { + CommentSlimView { + comment: self.comment, + creator: self.creator, + counts: self.counts, + creator_banned_from_community: self.creator_banned_from_community, + banned_from_community: self.banned_from_community, + creator_is_moderator: self.creator_is_moderator, + creator_is_admin: self.creator_is_admin, + subscribed: self.subscribed, + saved: self.saved, + creator_blocked: self.creator_blocked, + my_vote: self.my_vote, + } + } +} + +#[derive(Default)] +pub struct CommentQuery<'a> { + pub listing_type: Option, + pub sort: Option, + pub community_id: Option, + pub post_id: Option, + pub parent_path: Option, + pub creator_id: Option, + pub local_user: Option<&'a LocalUser>, + pub search_term: Option, + pub liked_only: Option, + pub disliked_only: Option, + pub page: Option, + pub limit: Option, + pub max_depth: Option, +} + +impl CommentQuery<'_> { + pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + let o = self; + // The left join below will return None in this case - let local_user_id_join = o.local_user.local_user_id().unwrap_or(LocalUserId(-1)); + let my_person_id = o.local_user.person_id(); + let local_user_id = o.local_user.local_user_id(); - let mut query = all_joins(comment::table.into_boxed(), o.local_user.person_id()); + let mut query = CommentView::joins(my_person_id) + .select(CommentView::as_select()) + .into_boxed(); if let Some(creator_id) = o.creator_id { query = query.filter(comment::creator_id.eq(creator_id)); @@ -186,7 +211,7 @@ fn queries<'a>() -> Queries< } } - if let Some(my_id) = o.local_user.person_id() { + if let Some(my_id) = my_person_id { let not_creator_filter = comment::creator_id.ne(my_id); if o.liked_only.unwrap_or_default() { query = query @@ -209,7 +234,11 @@ fn queries<'a>() -> Queries< local_user_language::table.filter( comment::language_id .eq(local_user_language::language_id) - .and(local_user_language::local_user_id.eq(local_user_id_join)), + .and( + local_user_language::local_user_id + .nullable() + .eq(local_user_id), + ), ), )); @@ -287,74 +316,17 @@ fn queries<'a>() -> Queries< CommentSortType::Top => query.then_order_by(comment_aggregates::score.desc()), }; - // Note: deleted and removed comments are done on the front side - query + let res = query .limit(limit) .offset(offset) - .load::(&mut conn) - .await - }; + .load::(conn) + .await?; - Queries::new(read, list) -} + let is_admin = o.local_user.map(|u| u.admin).unwrap_or(false); -impl CommentView { - pub async fn read( - pool: &mut DbPool<'_>, - comment_id: CommentId, - my_local_user: Option<&'_ LocalUser>, - ) -> Result { - let is_admin = my_local_user.map(|u| u.admin).unwrap_or(false); - // If a person is given, then my_vote (res.9), if None, should be 0, not null - // Necessary to differentiate between other person's votes - let mut res = queries().read(pool, (comment_id, my_local_user)).await?; - if my_local_user.is_some() && res.my_vote.is_none() { - res.my_vote = Some(0); - } - Ok(handle_deleted(res, is_admin)) - } - - pub fn map_to_slim(self) -> CommentSlimView { - CommentSlimView { - comment: self.comment, - creator: self.creator, - counts: self.counts, - creator_banned_from_community: self.creator_banned_from_community, - banned_from_community: self.banned_from_community, - creator_is_moderator: self.creator_is_moderator, - creator_is_admin: self.creator_is_admin, - subscribed: self.subscribed, - saved: self.saved, - creator_blocked: self.creator_blocked, - my_vote: self.my_vote, - } - } -} - -#[derive(Default)] -pub struct CommentQuery<'a> { - pub listing_type: Option, - pub sort: Option, - pub community_id: Option, - pub post_id: Option, - pub parent_path: Option, - pub creator_id: Option, - pub local_user: Option<&'a LocalUser>, - pub search_term: Option, - pub liked_only: Option, - pub disliked_only: Option, - pub page: Option, - pub limit: Option, - pub max_depth: Option, -} - -impl CommentQuery<'_> { - pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result, Error> { - let is_admin = self.local_user.map(|u| u.admin).unwrap_or(false); + // Note: deleted and removed comments are done on the front side Ok( - queries() - .list(pool, (self, site)) - .await? + res .into_iter() .map(|c| handle_deleted(c, is_admin)) .collect(), @@ -362,6 +334,7 @@ impl CommentQuery<'_> { } } +/// Only show deleted / removed content for admins. fn handle_deleted(mut c: CommentView, is_admin: bool) -> CommentView { if !is_admin && (c.comment.deleted || c.comment.removed) { c.comment.content = String::new(); diff --git a/crates/db_views/src/community/community_follower_view.rs b/crates/db_views/src/community/community_follower_view.rs index 487df6323..e439c14b9 100644 --- a/crates/db_views/src/community/community_follower_view.rs +++ b/crates/db_views/src/community/community_follower_view.rs @@ -8,6 +8,7 @@ use diesel::{ ExpressionMethods, JoinOnDsl, QueryDsl, + SelectableHelper, }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ @@ -17,13 +18,20 @@ use lemmy_db_schema::{ community::{Community, CommunityFollower, CommunityFollowerState}, person::Person, }, - utils::{action_query, get_conn, limit_and_offset, DbPool}, + utils::{get_conn, limit_and_offset, DbPool}, CommunityVisibility, SubscribedType, }; use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; impl CommunityFollowerView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins() -> _ { + community_actions::table + .filter(community_actions::followed.is_not_null()) + .inner_join(community::table) + .inner_join(person::table.on(community_actions::person_id.eq(person::id))) + } /// return a list of local community ids and remote inboxes that at least one user of the given /// instance has followed pub async fn get_instance_followed_community_inboxes( @@ -39,9 +47,7 @@ impl CommunityFollowerView { // that would work for all instances that support fully shared inboxes. // It would be a bit more complicated though to keep it in sync. - community_actions::table - .inner_join(community::table) - .inner_join(person::table.on(community_actions::person_id.eq(person::id))) + Self::joins() .filter(person::instance_id.eq(instance_id)) .filter(community::local) // this should be a no-op since community_followers table only has // local-person+remote-community or remote-person+local-community @@ -53,12 +59,29 @@ impl CommunityFollowerView { .await .with_lemmy_type(LemmyErrorType::NotFound) } + + pub async fn get_community_follower_inboxes( + pool: &mut DbPool<'_>, + community_id: CommunityId, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + let res = Self::joins() + .filter(community_actions::community_id.eq(community_id)) + .filter(not(person::local)) + .select(person::inbox_url) + .distinct() + .load::(conn) + .await?; + + Ok(res) + } + pub async fn count_community_followers( pool: &mut DbPool<'_>, community_id: CommunityId, ) -> Result { let conn = &mut get_conn(pool).await?; - let res = action_query(community_actions::followed) + let res = Self::joins() .filter(community_actions::community_id.eq(community_id)) .select(count_star()) .first::(conn) @@ -69,13 +92,11 @@ impl CommunityFollowerView { pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { let conn = &mut get_conn(pool).await?; - action_query(community_actions::followed) - .inner_join(community::table) - .inner_join(person::table.on(community_actions::person_id.eq(person::id))) - .select((community::all_columns, person::all_columns)) + Self::joins() .filter(community_actions::person_id.eq(person_id)) .filter(community::deleted.eq(false)) .filter(community::removed.eq(false)) + .select(Self::as_select()) .order_by(community::title) .load::(conn) .await @@ -124,9 +145,13 @@ impl CommunityFollowerView { ), )); - let mut query = action_query(community_actions::followed) - .inner_join(person::table.on(community_actions::person_id.eq(person::id))) - .inner_join(community::table) + let mut query = Self::joins() + .select(( + person::all_columns, + community::all_columns, + is_new_instance, + CommunityFollower::select_subscribed_type(), + )) .into_boxed(); if all_communities { // if param is false, only return items for communities where user is a mod @@ -142,12 +167,6 @@ impl CommunityFollowerView { .order_by(community_actions::followed.asc()) .limit(limit) .offset(offset) - .select(( - person::all_columns, - community::all_columns, - is_new_instance, - CommunityFollower::select_subscribed_type(), - )) .load::<(Person, Community, bool, SubscribedType)>(conn) .await?; Ok( @@ -170,8 +189,7 @@ impl CommunityFollowerView { community_id: CommunityId, ) -> Result { let conn = &mut get_conn(pool).await?; - action_query(community_actions::followed) - .inner_join(person::table.on(community_actions::person_id.eq(person::id))) + Self::joins() .filter(community_actions::community_id.eq(community_id)) .filter(community_actions::follow_state.eq(CommunityFollowerState::ApprovalRequired)) .select(count(community_actions::community_id)) @@ -188,7 +206,7 @@ impl CommunityFollowerView { } let conn = &mut get_conn(pool).await?; select(exists( - action_query(community_actions::followed) + Self::joins() .filter(community_actions::community_id.eq(community.id)) .filter(community_actions::person_id.eq(from_person_id)) .filter(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), @@ -205,8 +223,7 @@ impl CommunityFollowerView { ) -> Result<(), Error> { let conn = &mut get_conn(pool).await?; select(exists( - action_query(community_actions::followed) - .inner_join(person::table.on(community_actions::person_id.eq(person::id))) + Self::joins() .filter(community_actions::community_id.eq(community_id)) .filter(person::instance_id.eq(instance_id)) .filter(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), @@ -224,8 +241,7 @@ impl CommunityFollowerView { ) -> Result<(), Error> { let conn = &mut get_conn(pool).await?; select(exists( - action_query(community_actions::followed) - .inner_join(person::table.on(community_actions::person_id.eq(person::id))) + Self::joins() .filter(community_actions::community_id.eq(community_id)) .filter(person::instance_id.eq(instance_id)) .filter(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), diff --git a/crates/db_views/src/community/community_moderator_view.rs b/crates/db_views/src/community/community_moderator_view.rs index a9ada92e1..ad60675aa 100644 --- a/crates/db_views/src/community/community_moderator_view.rs +++ b/crates/db_views/src/community/community_moderator_view.rs @@ -1,26 +1,43 @@ use crate::structs::CommunityModeratorView; -use diesel::{dsl::exists, result::Error, select, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel::{ + dsl::exists, + result::Error, + select, + ExpressionMethods, + JoinOnDsl, + QueryDsl, + SelectableHelper, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ impls::local_user::LocalUserOptionHelper, newtypes::{CommunityId, PersonId}, schema::{community, community_actions, person}, source::local_user::LocalUser, - utils::{action_query, find_action, get_conn, DbPool}, + utils::{get_conn, DbPool}, }; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl CommunityModeratorView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins() -> _ { + community_actions::table + .filter(community_actions::became_moderator.is_not_null()) + .inner_join(community::table) + .inner_join(person::table.on(person::id.eq(community_actions::person_id))) + } + pub async fn check_is_community_moderator( pool: &mut DbPool<'_>, - find_community_id: CommunityId, - find_person_id: PersonId, + community_id: CommunityId, + person_id: PersonId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(exists(find_action( - community_actions::became_moderator, - (find_person_id, find_community_id), - ))) + select(exists( + Self::joins() + .filter(community_actions::person_id.eq(person_id)) + .filter(community_actions::community_id.eq(community_id)), + )) .get_result::(conn) .await? .then_some(()) @@ -29,12 +46,11 @@ impl CommunityModeratorView { pub(crate) async fn is_community_moderator_of_any( pool: &mut DbPool<'_>, - find_person_id: PersonId, + person_id: PersonId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; select(exists( - action_query(community_actions::became_moderator) - .filter(community_actions::person_id.eq(find_person_id)), + Self::joins().filter(community_actions::person_id.eq(person_id)), )) .get_result::(conn) .await? @@ -47,13 +63,11 @@ impl CommunityModeratorView { community_id: CommunityId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - action_query(community_actions::became_moderator) - .inner_join(community::table) - .inner_join(person::table.on(person::id.eq(community_actions::person_id))) + Self::joins() .filter(community_actions::community_id.eq(community_id)) - .select((community::all_columns, person::all_columns)) + .select(Self::as_select()) .order_by(community_actions::became_moderator) - .load::(conn) + .load::(conn) .await } @@ -63,11 +77,9 @@ impl CommunityModeratorView { local_user: Option<&LocalUser>, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - let mut query = action_query(community_actions::became_moderator) - .inner_join(community::table) - .inner_join(person::table.on(person::id.eq(community_actions::person_id))) + let mut query = Self::joins() .filter(community_actions::person_id.eq(person_id)) - .select((community::all_columns, person::all_columns)) + .select(Self::as_select()) .into_boxed(); query = local_user.visible_communities_only(query); @@ -82,17 +94,15 @@ impl CommunityModeratorView { query = query.filter(community::removed.eq(false)) } - query.load::(conn).await + query.load::(conn).await } /// Finds all communities first mods / creators /// 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, Error> { let conn = &mut get_conn(pool).await?; - action_query(community_actions::became_moderator) - .inner_join(community::table) - .inner_join(person::table.on(person::id.eq(community_actions::person_id))) - .select((community::all_columns, person::all_columns)) + Self::joins() + .select(Self::as_select()) // A hacky workaround instead of group_bys // https://stackoverflow.com/questions/24042359/how-to-join-only-one-row-in-joined-table-with-postgres .distinct_on(community_actions::community_id) @@ -100,7 +110,7 @@ impl CommunityModeratorView { community_actions::community_id, community_actions::became_moderator, )) - .load::(conn) + .load::(conn) .await } } diff --git a/crates/db_views/src/community/community_person_ban_view.rs b/crates/db_views/src/community/community_person_ban_view.rs index 224ea8d53..4c89046dc 100644 --- a/crates/db_views/src/community/community_person_ban_view.rs +++ b/crates/db_views/src/community/community_person_ban_view.rs @@ -2,12 +2,14 @@ use crate::structs::CommunityPersonBanView; use diesel::{ dsl::{exists, not}, select, + ExpressionMethods, + QueryDsl, }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, schema::community_actions, - utils::{find_action, get_conn, DbPool}, + utils::{get_conn, DbPool}, }; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; @@ -18,13 +20,13 @@ impl CommunityPersonBanView { from_community_id: CommunityId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(not(exists(find_action( - community_actions::received_ban, - (from_person_id, from_community_id), - )))) - .get_result::(conn) - .await? - .then_some(()) - .ok_or(LemmyErrorType::PersonIsBannedFromCommunity.into()) + let find_action = community_actions::table + .find((from_person_id, from_community_id)) + .filter(community_actions::received_ban.is_not_null()); + select(not(exists(find_action))) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::PersonIsBannedFromCommunity.into()) } } diff --git a/crates/db_views/src/community/community_view.rs b/crates/db_views/src/community/community_view.rs index fa9144c4b..2728d0926 100644 --- a/crates/db_views/src/community/community_view.rs +++ b/crates/db_views/src/community/community_view.rs @@ -1,12 +1,12 @@ use crate::structs::{CommunityModeratorView, CommunitySortType, CommunityView, PersonView}; use diesel::{ - pg::Pg, result::Error, BoolExpressionMethods, ExpressionMethods, + JoinOnDsl, NullableExpressionMethods, - PgTextExpressionMethods, QueryDsl, + SelectableHelper, }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ @@ -14,175 +14,56 @@ use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, schema::{community, community_actions, community_aggregates, instance_actions}, source::{ - community::{CommunityFollower, CommunityFollowerState}, + community::{Community, CommunityFollowerState}, local_user::LocalUser, site::Site, }, - utils::{ - actions, - functions::lower, - fuzzy_search, - limit_and_offset, - DbConn, - DbPool, - ListFn, - Queries, - ReadFn, - }, + utils::{functions::lower, get_conn, limit_and_offset, DbPool}, ListingType, - PostSortType, }; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; -type QueriesReadTypes<'a> = (CommunityId, Option<&'a LocalUser>, bool); -type QueriesListTypes<'a> = (CommunityQuery<'a>, &'a Site); - -fn queries<'a>() -> Queries< - impl ReadFn<'a, CommunityView, QueriesReadTypes<'a>>, - impl ListFn<'a, CommunityView, QueriesListTypes<'a>>, -> { - let all_joins = |query: community::BoxedQuery<'a, Pg>, my_local_user: Option<&'a LocalUser>| { - query - .inner_join(community_aggregates::table) - .left_join(actions( - community_actions::table, - my_local_user.person_id(), - community::id, - )) - .left_join(actions( - instance_actions::table, - my_local_user.person_id(), - community::instance_id, - )) - }; - - let selection = ( - community::all_columns, - CommunityFollower::select_subscribed_type(), - community_actions::blocked.nullable().is_not_null(), - community_aggregates::all_columns, - community_actions::received_ban.nullable().is_not_null(), - ); - - let not_removed_or_deleted = community::removed - .eq(false) - .and(community::deleted.eq(false)); - - let read = move |mut conn: DbConn<'a>, - (community_id, my_local_user, is_mod_or_admin): ( - CommunityId, - Option<&'a LocalUser>, - bool, - )| async move { - let mut query = all_joins( - community::table.find(community_id).into_boxed(), - my_local_user, - ) - .select(selection); - - // Hide deleted and removed for non-admins or mods - if !is_mod_or_admin { - query = query.filter(not_removed_or_deleted); - } - - query = my_local_user.visible_communities_only(query); - - query.first(&mut conn).await - }; - - let list = move |mut conn: DbConn<'a>, (o, site): (CommunityQuery<'a>, &'a Site)| async move { - use CommunitySortType::*; - - let mut query = all_joins(community::table.into_boxed(), o.local_user).select(selection); - - if let Some(search_term) = o.search_term { - let searcher = fuzzy_search(&search_term); - let name_filter = community::name.ilike(searcher.clone()); - let title_filter = community::title.ilike(searcher.clone()); - let description_filter = community::description.ilike(searcher.clone()); - query = if o.title_only.unwrap_or_default() { - query.filter(name_filter.or(title_filter)) - } else { - query.filter(name_filter.or(title_filter.or(description_filter))) - } - } - - // Hide deleted and removed for non-admins or mods - if !o.is_mod_or_admin { - query = query.filter(not_removed_or_deleted).filter( - community::hidden - .eq(false) - .or(community_actions::follow_state.is_not_null()), - ); - } - - match o.sort.unwrap_or(Hot) { - Hot | Active | Scaled => query = query.order_by(community_aggregates::hot_rank.desc()), - NewComments | TopDay | TopTwelveHour | TopSixHour | TopHour => { - query = query.order_by(community_aggregates::users_active_day.desc()) - } - New => query = query.order_by(community::published.desc()), - Old => query = query.order_by(community::published.asc()), - // Controversial is temporary until a CommentSortType is created - MostComments | Controversial => query = query.order_by(community_aggregates::comments.desc()), - TopAll | TopYear | TopNineMonths => { - query = query.order_by(community_aggregates::subscribers.desc()) - } - TopSixMonths | TopThreeMonths => { - query = query.order_by(community_aggregates::users_active_half_year.desc()) - } - TopMonth => query = query.order_by(community_aggregates::users_active_month.desc()), - TopWeek => query = query.order_by(community_aggregates::users_active_week.desc()), - NameAsc => query = query.order_by(lower(community::name).asc()), - NameDesc => query = query.order_by(lower(community::name).desc()), - }; - - let is_subscribed = community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted)); - - if let Some(listing_type) = o.listing_type { - query = match listing_type { - ListingType::All => query.filter(community::hidden.eq(false).or(is_subscribed)), - ListingType::Subscribed => query.filter(is_subscribed), - ListingType::Local => query - .filter(community::local.eq(true)) - .filter(community::hidden.eq(false).or(is_subscribed)), - ListingType::ModeratorView => { - query.filter(community_actions::became_moderator.is_not_null()) - } - }; - } - - // Don't show blocked communities and communities on blocked instances. nsfw communities are - // also hidden (based on profile setting) - query = query.filter(instance_actions::blocked.is_null()); - query = query.filter(community_actions::blocked.is_null()); - if !(o.local_user.show_nsfw(site) || o.show_nsfw) { - query = query.filter(community::nsfw.eq(false)); - } - - query = o.local_user.visible_communities_only(query); - - let (limit, offset) = limit_and_offset(o.page, o.limit)?; - query - .limit(limit) - .offset(offset) - .load::(&mut conn) - .await - }; - - Queries::new(read, list) -} - impl CommunityView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(person_id: Option) -> _ { + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(community::id) + .and(community_actions::person_id.nullable().eq(person_id)), + ); + + let instance_actions_join = instance_actions::table.on( + instance_actions::instance_id + .eq(community::instance_id) + .and(instance_actions::person_id.nullable().eq(person_id)), + ); + + community::table + .inner_join(community_aggregates::table) + .left_join(community_actions_join) + .left_join(instance_actions_join) + } + pub async fn read( pool: &mut DbPool<'_>, community_id: CommunityId, my_local_user: Option<&'_ LocalUser>, is_mod_or_admin: bool, ) -> Result { - queries() - .read(pool, (community_id, my_local_user, is_mod_or_admin)) - .await + let conn = &mut get_conn(pool).await?; + let mut query = Self::joins(my_local_user.person_id()) + .filter(community::id.eq(community_id)) + .select(Self::as_select()) + .into_boxed(); + + // Hide deleted and removed for non-admins or mods + if !is_mod_or_admin { + query = query.filter(Community::hide_removed_and_deleted()); + } + + query = my_local_user.visible_communities_only(query); + + query.first(conn).await } pub async fn check_is_mod_or_admin( @@ -222,38 +103,11 @@ impl CommunityView { } } -impl From for CommunitySortType { - fn from(value: PostSortType) -> Self { - match value { - PostSortType::Active => Self::Active, - PostSortType::Hot => Self::Hot, - PostSortType::New => Self::New, - PostSortType::Old => Self::Old, - PostSortType::TopDay => Self::TopDay, - PostSortType::TopWeek => Self::TopWeek, - PostSortType::TopMonth => Self::TopMonth, - PostSortType::TopYear => Self::TopYear, - PostSortType::TopAll => Self::TopAll, - PostSortType::MostComments => Self::MostComments, - PostSortType::NewComments => Self::NewComments, - PostSortType::TopHour => Self::TopHour, - PostSortType::TopSixHour => Self::TopSixHour, - PostSortType::TopTwelveHour => Self::TopTwelveHour, - PostSortType::TopThreeMonths => Self::TopThreeMonths, - PostSortType::TopSixMonths => Self::TopSixMonths, - PostSortType::TopNineMonths => Self::TopNineMonths, - PostSortType::Controversial => Self::Controversial, - PostSortType::Scaled => Self::Scaled, - } - } -} - #[derive(Default)] pub struct CommunityQuery<'a> { pub listing_type: Option, pub sort: Option, pub local_user: Option<&'a LocalUser>, - pub search_term: Option, pub title_only: Option, pub is_mod_or_admin: bool, pub show_nsfw: bool, @@ -263,7 +117,73 @@ pub struct CommunityQuery<'a> { impl CommunityQuery<'_> { pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result, Error> { - queries().list(pool, (self, site)).await + use CommunitySortType::*; + let conn = &mut get_conn(pool).await?; + let o = self; + + let mut query = CommunityView::joins(o.local_user.person_id()) + .select(CommunityView::as_select()) + .into_boxed(); + + // Hide deleted and removed for non-admins or mods + if !o.is_mod_or_admin { + query = query.filter(Community::hide_removed_and_deleted()).filter( + community::hidden + .eq(false) + .or(community_actions::follow_state.is_not_null()), + ); + } + + let is_subscribed = community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted)); + + if let Some(listing_type) = o.listing_type { + query = match listing_type { + ListingType::All => query.filter(community::hidden.eq(false).or(is_subscribed)), + ListingType::Subscribed => query.filter(is_subscribed), + ListingType::Local => query + .filter(community::local.eq(true)) + .filter(community::hidden.eq(false).or(is_subscribed)), + ListingType::ModeratorView => { + query.filter(community_actions::became_moderator.is_not_null()) + } + }; + } + + // Don't show blocked communities and communities on blocked instances. nsfw communities are + // also hidden (based on profile setting) + query = query.filter(instance_actions::blocked.is_null()); + query = query.filter(community_actions::blocked.is_null()); + if !(o.local_user.show_nsfw(site) || o.show_nsfw) { + query = query.filter(community::nsfw.eq(false)); + } + + query = o.local_user.visible_communities_only(query); + + match o.sort.unwrap_or_default() { + Hot => query = query.order_by(community_aggregates::hot_rank.desc()), + Comments => query = query.order_by(community_aggregates::comments.desc()), + Posts => query = query.order_by(community_aggregates::posts.desc()), + New => query = query.order_by(community::published.desc()), + Old => query = query.order_by(community::published.asc()), + Subscribers => query = query.order_by(community_aggregates::subscribers.desc()), + SubscribersLocal => query = query.order_by(community_aggregates::subscribers_local.desc()), + ActiveSixMonths => { + query = query.order_by(community_aggregates::users_active_half_year.desc()) + } + ActiveMonthly => query = query.order_by(community_aggregates::users_active_month.desc()), + ActiveWeekly => query = query.order_by(community_aggregates::users_active_week.desc()), + ActiveDaily => query = query.order_by(community_aggregates::users_active_day.desc()), + NameAsc => query = query.order_by(lower(community::name).asc()), + NameDesc => query = query.order_by(lower(community::name).desc()), + }; + + let (limit, offset) = limit_and_offset(o.page, o.limit)?; + + query + .limit(limit) + .offset(offset) + .load::(conn) + .await } } diff --git a/crates/db_views/src/local_user/local_user_view.rs b/crates/db_views/src/local_user/local_user_view.rs index 68072cb5a..6525d5ca7 100644 --- a/crates/db_views/src/local_user/local_user_view.rs +++ b/crates/db_views/src/local_user/local_user_view.rs @@ -1,6 +1,13 @@ use crate::structs::LocalUserView; use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest}; -use diesel::{result::Error, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel::{ + result::Error, + BoolExpressionMethods, + ExpressionMethods, + JoinOnDsl, + QueryDsl, + SelectableHelper, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{LocalUserId, OAuthProviderId, PersonId}, @@ -13,118 +20,72 @@ use lemmy_db_schema::{ traits::Crud, utils::{ functions::{coalesce, lower}, - DbConn, + get_conn, DbPool, - ListFn, - Queries, - ReadFn, }, }; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; use std::future::{ready, Ready}; -enum ReadBy<'a> { - Id(LocalUserId), - Person(PersonId), - Name(&'a str), - NameOrEmail(&'a str), - Email(&'a str), - OAuthID(OAuthProviderId, &'a str), -} - -enum ListMode { - AdminsWithEmails, -} - -fn queries<'a>( -) -> Queries>, impl ListFn<'a, LocalUserView, ListMode>> { - let selection = ( - local_user::all_columns, - local_user_vote_display_mode::all_columns, - person::all_columns, - person_aggregates::all_columns, - ); - - let read = move |mut conn: DbConn<'a>, search: ReadBy<'a>| async move { - let mut query = local_user::table.into_boxed(); - query = match search { - ReadBy::Id(local_user_id) => query.filter(local_user::id.eq(local_user_id)), - ReadBy::Email(from_email) => { - query.filter(lower(coalesce(local_user::email, "")).eq(from_email.to_lowercase())) - } - _ => query, - }; - let mut query = query.inner_join(person::table); - query = match search { - ReadBy::Person(person_id) => query.filter(person::id.eq(person_id)), - ReadBy::Name(name) => query.filter(lower(person::name).eq(name.to_lowercase())), - ReadBy::NameOrEmail(name_or_email) => query.filter( - lower(person::name) - .eq(lower(name_or_email.to_lowercase())) - .or(lower(coalesce(local_user::email, "")).eq(name_or_email.to_lowercase())), - ), - _ => query, - }; - let query = query - .inner_join(local_user_vote_display_mode::table) - .inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id))); - - if let ReadBy::OAuthID(oauth_provider_id, oauth_user_id) = search { - query - .inner_join(oauth_account::table) - .filter(oauth_account::oauth_provider_id.eq(oauth_provider_id)) - .filter(oauth_account::oauth_user_id.eq(oauth_user_id)) - .select(selection) - .first(&mut conn) - .await - } else { - query.select(selection).first(&mut conn).await - } - }; - - let list = move |mut conn: DbConn<'a>, mode: ListMode| async move { - match mode { - ListMode::AdminsWithEmails => { - local_user::table - .inner_join(local_user_vote_display_mode::table) - .inner_join(person::table) - .inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id))) - .filter(local_user::email.is_not_null()) - .filter(local_user::admin.eq(true)) - .select(selection) - .load::(&mut conn) - .await - } - } - }; - - Queries::new(read, list) -} - impl LocalUserView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins() -> _ { + local_user::table + .inner_join(local_user_vote_display_mode::table) + .inner_join(person::table) + .inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id))) + } + pub async fn read(pool: &mut DbPool<'_>, local_user_id: LocalUserId) -> Result { - queries().read(pool, ReadBy::Id(local_user_id)).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(local_user::id.eq(local_user_id)) + .select(Self::as_select()) + .first(conn) + .await } pub async fn read_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { - queries().read(pool, ReadBy::Person(person_id)).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(person::id.eq(person_id)) + .select(Self::as_select()) + .first(conn) + .await } pub async fn read_from_name(pool: &mut DbPool<'_>, name: &str) -> Result { - queries().read(pool, ReadBy::Name(name)).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(lower(person::name).eq(name.to_lowercase())) + .select(Self::as_select()) + .first(conn) + .await } pub async fn find_by_email_or_name( pool: &mut DbPool<'_>, name_or_email: &str, ) -> Result { - queries() - .read(pool, ReadBy::NameOrEmail(name_or_email)) + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter( + lower(person::name) + .eq(lower(name_or_email.to_lowercase())) + .or(lower(coalesce(local_user::email, "")).eq(name_or_email.to_lowercase())), + ) + .select(Self::as_select()) + .first(conn) .await } pub async fn find_by_email(pool: &mut DbPool<'_>, from_email: &str) -> Result { - queries().read(pool, ReadBy::Email(from_email)).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(lower(coalesce(local_user::email, "")).eq(from_email.to_lowercase())) + .select(Self::as_select()) + .first(conn) + .await } pub async fn find_by_oauth_id( @@ -132,13 +93,24 @@ impl LocalUserView { oauth_provider_id: OAuthProviderId, oauth_user_id: &str, ) -> Result { - queries() - .read(pool, ReadBy::OAuthID(oauth_provider_id, oauth_user_id)) + let conn = &mut get_conn(pool).await?; + Self::joins() + .inner_join(oauth_account::table) + .filter(oauth_account::oauth_provider_id.eq(oauth_provider_id)) + .filter(oauth_account::oauth_user_id.eq(oauth_user_id)) + .select(Self::as_select()) + .first(conn) .await } pub async fn list_admins_with_emails(pool: &mut DbPool<'_>) -> Result, Error> { - queries().list(pool, ListMode::AdminsWithEmails).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(local_user::email.is_not_null()) + .filter(local_user::admin.eq(true)) + .select(Self::as_select()) + .load::(conn) + .await } pub async fn create_test_user( diff --git a/crates/db_views/src/person/person_view.rs b/crates/db_views/src/person/person_view.rs index 5c1a3ae70..e0a216064 100644 --- a/crates/db_views/src/person/person_view.rs +++ b/crates/db_views/src/person/person_view.rs @@ -1,173 +1,71 @@ use crate::structs::PersonView; use diesel::{ - pg::Pg, result::Error, BoolExpressionMethods, ExpressionMethods, NullableExpressionMethods, - PgTextExpressionMethods, QueryDsl, + SelectableHelper, }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::PersonId, schema::{local_user, person, person_aggregates}, - utils::{ - functions::coalesce, - fuzzy_search, - limit_and_offset, - now, - DbConn, - DbPool, - ListFn, - Queries, - ReadFn, - }, - ListingType, - PostSortType, + utils::{get_conn, now, DbPool}, }; -use serde::{Deserialize, Serialize}; -use strum::{Display, EnumString}; - -enum ListMode { - Admins, - Banned, - Query(PersonQuery), -} - -#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)] -/// The person sort types. Converted automatically from `SortType` -enum PersonSortType { - New, - Old, - MostComments, - CommentScore, - PostScore, - PostCount, -} - -fn post_to_person_sort_type(sort: PostSortType) -> PersonSortType { - use PostSortType::*; - match sort { - Active | Hot | Controversial => PersonSortType::CommentScore, - New | NewComments => PersonSortType::New, - MostComments => PersonSortType::MostComments, - Old => PersonSortType::Old, - _ => PersonSortType::CommentScore, - } -} - -fn queries<'a>( -) -> Queries, impl ListFn<'a, PersonView, ListMode>> { - let all_joins = move |query: person::BoxedQuery<'a, Pg>| { - query - .inner_join(person_aggregates::table) - .left_join(local_user::table) - .select(( - person::all_columns, - person_aggregates::all_columns, - coalesce(local_user::admin.nullable(), false), - )) - }; - - let read = move |mut conn: DbConn<'a>, params: (PersonId, bool)| async move { - let (person_id, is_admin) = params; - let mut query = all_joins(person::table.find(person_id).into_boxed()); - if !is_admin { - query = query.filter(person::deleted.eq(false)); - } - query.first(&mut conn).await - }; - - let list = move |mut conn: DbConn<'a>, mode: ListMode| async move { - let mut query = all_joins(person::table.into_boxed()).filter(person::deleted.eq(false)); - match mode { - ListMode::Admins => { - query = query - .filter(local_user::admin.eq(true)) - .filter(person::deleted.eq(false)) - .order_by(person::published); - } - ListMode::Banned => { - query = query - .filter( - person::local.eq(true).and( - person::banned.eq(true).and( - person::ban_expires - .is_null() - .or(person::ban_expires.gt(now().nullable())), - ), - ), - ) - .filter(person::deleted.eq(false)); - } - ListMode::Query(o) => { - if let Some(search_term) = o.search_term { - let searcher = fuzzy_search(&search_term); - query = query - .filter(person::name.ilike(searcher.clone())) - .or_filter(person::display_name.ilike(searcher)); - } - - let sort = o.sort.map(post_to_person_sort_type); - query = match sort.unwrap_or(PersonSortType::CommentScore) { - PersonSortType::New => query.order_by(person::published.desc()), - PersonSortType::Old => query.order_by(person::published.asc()), - PersonSortType::MostComments => query.order_by(person_aggregates::comment_count.desc()), - PersonSortType::CommentScore => query.order_by(person_aggregates::comment_score.desc()), - PersonSortType::PostScore => query.order_by(person_aggregates::post_score.desc()), - PersonSortType::PostCount => query.order_by(person_aggregates::post_count.desc()), - }; - - let (limit, offset) = limit_and_offset(o.page, o.limit)?; - query = query.limit(limit).offset(offset); - - if let Some(listing_type) = o.listing_type { - query = match listing_type { - // return nothing as its not possible to follow users - ListingType::Subscribed => query.limit(0), - ListingType::Local => query.filter(person::local.eq(true)), - _ => query, - }; - } - } - } - query.load::(&mut conn).await - }; - - Queries::new(read, list) -} impl PersonView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins() -> _ { + person::table + .inner_join(person_aggregates::table) + .left_join(local_user::table) + } + pub async fn read( pool: &mut DbPool<'_>, person_id: PersonId, is_admin: bool, ) -> Result { - queries().read(pool, (person_id, is_admin)).await + let conn = &mut get_conn(pool).await?; + let mut query = Self::joins() + .filter(person::id.eq(person_id)) + .select(Self::as_select()) + .into_boxed(); + + if !is_admin { + query = query.filter(person::deleted.eq(false)) + } + + query.first(conn).await } pub async fn admins(pool: &mut DbPool<'_>) -> Result, Error> { - queries().list(pool, ListMode::Admins).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(person::deleted.eq(false)) + .filter(local_user::admin.eq(true)) + .order_by(person::published) + .select(Self::as_select()) + .load::(conn) + .await } pub async fn banned(pool: &mut DbPool<'_>) -> Result, Error> { - queries().list(pool, ListMode::Banned).await - } -} - -#[derive(Default)] -pub struct PersonQuery { - pub sort: Option, - pub search_term: Option, - pub listing_type: Option, - pub page: Option, - pub limit: Option, -} - -impl PersonQuery { - pub async fn list(self, pool: &mut DbPool<'_>) -> Result, Error> { - queries().list(pool, ListMode::Query(self)).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(person::deleted.eq(false)) + .filter( + person::banned.eq(true).and( + person::ban_expires + .is_null() + .or(person::ban_expires.gt(now().nullable())), + ), + ) + .order_by(person::published) + .select(Self::as_select()) + .load::(conn) + .await } } @@ -258,15 +156,6 @@ mod tests { let read = PersonView::read(pool, data.alice.id, true).await; assert!(read.is_ok()); - let list = PersonQuery { - sort: Some(PostSortType::New), - ..Default::default() - } - .list(pool) - .await?; - assert_length!(1, list); - assert_eq!(list[0].person.id, data.bob.id); - cleanup(data, pool).await } @@ -323,31 +212,4 @@ mod tests { cleanup(data, pool).await } - - #[tokio::test] - #[serial] - async fn listing_type() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests(); - let pool = &mut pool.into(); - let data = init_data(pool).await?; - - let list = PersonQuery { - listing_type: Some(ListingType::Local), - ..Default::default() - } - .list(pool) - .await?; - assert_length!(1, list); - assert_eq!(list[0].person.id, data.alice.id); - - let list = PersonQuery { - listing_type: Some(ListingType::All), - ..Default::default() - } - .list(pool) - .await?; - assert_length!(2, list); - - cleanup(data, pool).await - } } diff --git a/crates/db_views/src/post/post_view.rs b/crates/db_views/src/post/post_view.rs index 7065b4637..0500733c9 100644 --- a/crates/db_views/src/post/post_view.rs +++ b/crates/db_views/src/post/post_view.rs @@ -19,7 +19,7 @@ use lemmy_db_schema::{ aggregates::structs::{post_aggregates_keys as key, PostAggregates}, aliases::creator_community_actions, impls::local_user::LocalUserOptionHelper, - newtypes::{CommunityId, LocalUserId, PersonId, PostId}, + newtypes::{CommunityId, PersonId, PostId}, schema::{ community, community_actions, @@ -42,9 +42,6 @@ use lemmy_db_schema::{ site::Site, }, utils::{ - action_query, - actions, - actions_alias, functions::coalesce, fuzzy_search, get_conn, @@ -52,11 +49,7 @@ use lemmy_db_schema::{ now, paginate, Commented, - DbConn, DbPool, - ListFn, - Queries, - ReadFn, ReverseTimestampKey, }, CommunityVisibility, @@ -66,37 +59,83 @@ use lemmy_db_schema::{ use tracing::debug; use PostSortType::*; -type QueriesReadTypes<'a> = (PostId, Option<&'a LocalUser>, bool); -type QueriesListTypes<'a> = (PostQuery<'a>, &'a Site); +impl PostView { + // TODO while we can abstract the joins into a function, the selects are currently impossible to + // do, because they rely on a few types that aren't yet publicly exported in diesel: + // https://github.com/diesel-rs/diesel/issues/4462 -fn queries<'a>() -> Queries< - impl ReadFn<'a, PostView, QueriesReadTypes<'a>>, - impl ListFn<'a, PostView, QueriesListTypes<'a>>, -> { - let creator_is_admin = exists( - local_user::table.filter( - post_aggregates::creator_id - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ); + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(my_person_id: Option) -> _ { + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(post_aggregates::community_id) + .and(community_actions::person_id.nullable().eq(my_person_id)), + ); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(post_aggregates::creator_id) + .and(person_actions::person_id.nullable().eq(my_person_id)), + ); + + let post_actions_join = post_actions::table.on( + post_actions::post_id + .eq(post_aggregates::post_id) + .and(post_actions::person_id.nullable().eq(my_person_id)), + ); + + let instance_actions_join = instance_actions::table.on( + instance_actions::instance_id + .eq(post_aggregates::instance_id) + .and(instance_actions::person_id.nullable().eq(my_person_id)), + ); + + let post_creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post_aggregates::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(post_aggregates::creator_id), + ), + ); + + let image_details_join = + image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())); + + post_aggregates::table + .inner_join(person::table) + .inner_join(community::table) + .inner_join(post::table) + .left_join(image_details_join) + .left_join(community_actions_join) + .left_join(person_actions_join) + .left_join(post_actions_join) + .left_join(instance_actions_join) + .left_join(post_creator_community_actions_join) + } + + #[diesel::dsl::auto_type(no_type_alias)] + fn creator_is_admin() -> _ { + exists( + local_user::table.filter( + post_aggregates::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ), + ) + } + + pub async fn read( + pool: &mut DbPool<'_>, + post_id: PostId, + my_local_user: Option<&'_ LocalUser>, + is_mod_or_admin: bool, + ) -> Result { + let conn = &mut get_conn(pool).await?; + let my_person_id = my_local_user.person_id(); - // TODO maybe this should go to localuser also - let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>, - my_person_id: Option| { - // We fetch post tags by letting postgresql aggregate them internally in a subquery into JSON. - // This is a simple way to join m rows into n rows without duplicating the data and getting - // complex diesel types. In pure SQL you would usually do this either using a LEFT JOIN + then - // aggregating the results in the application code. But this results in a lot of duplicate - // data transferred (since each post will be returned once per tag that it has) and more - // complicated application code. The diesel docs suggest doing three separate sequential queries - // in this case (see https://diesel.rs/guides/relations.html#many-to-many-or-mn ): First fetch - // the posts, then fetch all relevant post-tag-association tuples from the db, and then fetch - // all the relevant tag objects. - // - // If we want to filter by post tag we will have to add - // separate logic below since this subquery can't affect filtering, but it is simple (`WHERE - // exists (select 1 from post_community_post_tags where community_post_tag_id in (1,2,3,4)`). let post_tags = post_tag::table .inner_join(tag::table) .select(diesel::dsl::sql::( @@ -106,36 +145,8 @@ fn queries<'a>() -> Queries< .filter(tag::deleted.eq(false)) .single_value(); - query - .inner_join(person::table) - .inner_join(community::table) - .inner_join(post::table) - .left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable()))) - .left_join(actions( - community_actions::table, - my_person_id, - post_aggregates::community_id, - )) - .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, - )) + let mut query = Self::joins(my_person_id) + .filter(post_aggregates::post_id.eq(post_id)) .select(( post::all_columns, person::all_columns, @@ -150,7 +161,7 @@ fn queries<'a>() -> Queries< .field(community_actions::became_moderator) .nullable() .is_not_null(), - creator_is_admin, + Self::creator_is_admin(), post_aggregates::all_columns, CommunityFollower::select_subscribed_type(), post_actions::saved.nullable().is_not_null(), @@ -164,24 +175,7 @@ fn queries<'a>() -> Queries< ), post_tags, )) - }; - - let read = move |mut conn: DbConn<'a>, - (post_id, my_local_user, is_mod_or_admin): ( - PostId, - Option<&'a LocalUser>, - bool, - )| async move { - // The left join below will return None in this case - let my_person_id = my_local_user.person_id(); - let person_id_join = my_person_id.unwrap_or(PersonId(-1)); - - let mut query = all_joins( - post_aggregates::table - .filter(post_aggregates::post_id.eq(post_id)) - .into_boxed(), - my_person_id, - ); + .into_boxed(); // Hide deleted and removed for non-admins or mods if !is_mod_or_admin { @@ -189,23 +183,23 @@ fn queries<'a>() -> Queries< .filter( community::removed .eq(false) - .or(post::creator_id.eq(person_id_join)), + .or(post::creator_id.nullable().eq(my_person_id)), ) .filter( post::removed .eq(false) - .or(post::creator_id.eq(person_id_join)), + .or(post::creator_id.nullable().eq(my_person_id)), ) // users can see their own deleted posts .filter( community::deleted .eq(false) - .or(post::creator_id.eq(person_id_join)), + .or(post::creator_id.nullable().eq(my_person_id)), ) .filter( post::deleted .eq(false) - .or(post::creator_id.eq(person_id_join)), + .or(post::creator_id.nullable().eq(my_person_id)), ) // private communities can only by browsed by accepted followers .filter( @@ -219,18 +213,216 @@ fn queries<'a>() -> Queries< Commented::new(query) .text("PostView::read") - .first(&mut conn) + .first(conn) .await - }; + } +} - let list = move |mut conn: DbConn<'a>, (o, site): (PostQuery<'a>, &'a Site)| async move { - // The left join below will return None in this case - let local_user_id_join = o.local_user.local_user_id().unwrap_or(LocalUserId(-1)); - - let mut query = all_joins( - post_aggregates::table.into_boxed(), - o.local_user.person_id(), +impl PaginationCursor { + // get cursor for page that starts immediately after the given post + pub fn after_post(view: &PostView) -> PaginationCursor { + // hex encoding to prevent ossification + PaginationCursor(format!("P{:x}", view.counts.post_id.0)) + } + pub async fn read( + &self, + pool: &mut DbPool<'_>, + local_user: Option<&LocalUser>, + ) -> Result { + let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into()); + let post_id = PostId( + self + .0 + .get(1..) + .and_then(|e| i32::from_str_radix(e, 16).ok()) + .ok_or_else(err_msg)?, ); + let post_aggregates = PostAggregates::read(pool, post_id).await?; + let post_actions = PostActionsCursor::read(pool, post_id, local_user.person_id()).await?; + + Ok(PaginationCursorData { + post_aggregates, + post_actions, + }) + } +} + +// currently we use aggregates or actions as the pagination token. +// we only use some of the properties, depending on which sort type we page by +#[derive(Clone)] +pub struct PaginationCursorData { + post_aggregates: PostAggregates, + post_actions: PostActionsCursor, +} + +#[derive(Clone, Default)] +pub struct PostQuery<'a> { + pub listing_type: Option, + pub sort: Option, + pub creator_id: Option, + pub community_id: Option, + // if true, the query should be handled as if community_id was not given except adding the + // literal filter + pub community_id_just_for_prefetch: bool, + pub local_user: Option<&'a LocalUser>, + pub search_term: Option, + pub url_only: Option, + pub read_only: Option, + pub liked_only: Option, + pub disliked_only: Option, + pub title_only: Option, + pub page: Option, + pub limit: Option, + pub page_after: Option, + pub page_before_or_equal: Option, + pub page_back: Option, + pub show_hidden: Option, + pub show_read: Option, + pub show_nsfw: Option, + pub hide_media: Option, + pub no_comments_only: Option, +} + +impl<'a> PostQuery<'a> { + #[allow(clippy::expect_used)] + async fn prefetch_upper_bound_for_page_before( + &self, + site: &Site, + pool: &mut DbPool<'_>, + ) -> Result>, Error> { + // first get one page for the most popular community to get an upper bound for the page end for + // the real query. the reason this is needed is that when fetching posts for a single + // community PostgreSQL can optimize the query to use an index on e.g. (=, >=, >=, >=) and + // fetch only LIMIT rows but for the followed-communities query it has to query the index on + // (IN, >=, >=, >=) which it currently can't do at all (as of PG 16). see the discussion + // here: https://github.com/LemmyNet/lemmy/issues/2877#issuecomment-1673597190 + // + // the results are correct no matter which community we fetch these for, since it basically + // covers the "worst case" of the whole page consisting of posts from one community + // but using the largest community decreases the pagination-frame so make the real query more + // efficient. + use lemmy_db_schema::schema::community_aggregates::dsl::{ + community_aggregates, + community_id, + users_active_month, + }; + let (limit, offset) = limit_and_offset(self.page, self.limit)?; + if offset != 0 && self.page_after.is_some() { + return Err(Error::QueryBuilderError( + "legacy pagination cannot be combined with v2 pagination".into(), + )); + } + let self_person_id = self.local_user.expect("part of the above if").person_id; + let largest_subscribed = { + let conn = &mut get_conn(pool).await?; + community_actions::table + .filter(community_actions::followed.is_not_null()) + .filter(community_actions::person_id.eq(self_person_id)) + .inner_join(community_aggregates.on(community_id.eq(community_actions::community_id))) + .order_by(users_active_month.desc()) + .select(community_id) + .limit(1) + .get_result::(conn) + .await + .optional()? + }; + let Some(largest_subscribed) = largest_subscribed else { + // nothing subscribed to? no posts + return Ok(None); + }; + + let mut v = Box::pin( + PostQuery { + community_id: Some(largest_subscribed), + community_id_just_for_prefetch: true, + ..self.clone() + } + .list(site, pool), + ) + .await?; + + // take last element of array. if this query returned less than LIMIT elements, + // the heuristic is invalid since we can't guarantee the full query will return >= LIMIT results + // (return original query) + if (v.len() as i64) < limit { + Ok(Some(self.clone())) + } else { + let item = if self.page_back.unwrap_or_default() { + // for backward pagination, get first element instead + v.into_iter().next() + } else { + v.pop() + }; + let limit_cursor = Some(item.expect("else case").counts); + Ok(Some(PostQuery { + page_before_or_equal: limit_cursor, + ..self.clone() + })) + } + } + + pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result, Error> { + let o = if self.listing_type == Some(ListingType::Subscribed) + && self.community_id.is_none() + && self.local_user.is_some() + && self.page_before_or_equal.is_none() + { + if let Some(query) = self + .prefetch_upper_bound_for_page_before(site, pool) + .await? + { + query + } else { + self + } + } else { + self + }; + + let conn = &mut get_conn(pool).await?; + + let my_person_id = o.local_user.person_id(); + let my_local_user_id = o.local_user.local_user_id(); + + let post_tags = post_tag::table + .inner_join(tag::table) + .select(diesel::dsl::sql::( + "json_agg(tag.*)", + )) + .filter(post_tag::post_id.eq(post_aggregates::post_id)) + .filter(tag::deleted.eq(false)) + .single_value(); + + let mut query = PostView::joins(my_person_id) + .select(( + post::all_columns, + person::all_columns, + community::all_columns, + image_details::all_columns.nullable(), + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_not_null(), + community_actions::received_ban.nullable().is_not_null(), + creator_community_actions + .field(community_actions::became_moderator) + .nullable() + .is_not_null(), + PostView::creator_is_admin(), + post_aggregates::all_columns, + CommunityFollower::select_subscribed_type(), + post_actions::saved.nullable().is_not_null(), + post_actions::read.nullable().is_not_null(), + post_actions::hidden.nullable().is_not_null(), + person_actions::blocked.nullable().is_not_null(), + post_actions::like_score.nullable(), + coalesce( + post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(), + post_aggregates::comments, + ), + post_tags, + )) + .into_boxed(); // hide posts from deleted communities query = query.filter(community::deleted.eq(false)); @@ -360,9 +552,11 @@ fn queries<'a>() -> Queries< if o.local_user.is_some() { query = query.filter(exists( local_user_language::table.filter( - post::language_id - .eq(local_user_language::language_id) - .and(local_user_language::local_user_id.eq(local_user_id_join)), + post::language_id.eq(local_user_language::language_id).and( + local_user_language::local_user_id + .nullable() + .eq(my_local_user_id), + ), ), )); } @@ -448,188 +642,8 @@ fn queries<'a>() -> Queries< "getting upper bound for next query", o.community_id_just_for_prefetch, ) - .load::(&mut conn) + .load::(conn) .await - }; - - Queries::new(read, list) -} - -impl PostView { - pub async fn read( - pool: &mut DbPool<'_>, - post_id: PostId, - my_local_user: Option<&'_ LocalUser>, - is_mod_or_admin: bool, - ) -> Result { - queries() - .read(pool, (post_id, my_local_user, is_mod_or_admin)) - .await - } -} - -impl PaginationCursor { - // get cursor for page that starts immediately after the given post - pub fn after_post(view: &PostView) -> PaginationCursor { - // hex encoding to prevent ossification - PaginationCursor(format!("P{:x}", view.counts.post_id.0)) - } - pub async fn read( - &self, - pool: &mut DbPool<'_>, - local_user: Option<&LocalUser>, - ) -> Result { - let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into()); - let post_id = PostId( - self - .0 - .get(1..) - .and_then(|e| i32::from_str_radix(e, 16).ok()) - .ok_or_else(err_msg)?, - ); - let post_aggregates = PostAggregates::read(pool, post_id).await?; - let post_actions = PostActionsCursor::read(pool, post_id, local_user.person_id()).await?; - - Ok(PaginationCursorData { - post_aggregates, - post_actions, - }) - } -} - -// currently we use aggregates or actions as the pagination token. -// we only use some of the properties, depending on which sort type we page by -#[derive(Clone)] -pub struct PaginationCursorData { - post_aggregates: PostAggregates, - post_actions: PostActionsCursor, -} - -#[derive(Clone, Default)] -pub struct PostQuery<'a> { - pub listing_type: Option, - pub sort: Option, - pub creator_id: Option, - pub community_id: Option, - // if true, the query should be handled as if community_id was not given except adding the - // literal filter - pub community_id_just_for_prefetch: bool, - pub local_user: Option<&'a LocalUser>, - pub search_term: Option, - pub url_only: Option, - pub read_only: Option, - pub liked_only: Option, - pub disliked_only: Option, - pub title_only: Option, - pub page: Option, - pub limit: Option, - pub page_after: Option, - pub page_before_or_equal: Option, - pub page_back: Option, - pub show_hidden: Option, - pub show_read: Option, - pub show_nsfw: Option, - pub hide_media: Option, - pub no_comments_only: Option, -} - -impl<'a> PostQuery<'a> { - #[allow(clippy::expect_used)] - async fn prefetch_upper_bound_for_page_before( - &self, - site: &Site, - pool: &mut DbPool<'_>, - ) -> Result>, Error> { - // first get one page for the most popular community to get an upper bound for the page end for - // the real query. the reason this is needed is that when fetching posts for a single - // community PostgreSQL can optimize the query to use an index on e.g. (=, >=, >=, >=) and - // fetch only LIMIT rows but for the followed-communities query it has to query the index on - // (IN, >=, >=, >=) which it currently can't do at all (as of PG 16). see the discussion - // here: https://github.com/LemmyNet/lemmy/issues/2877#issuecomment-1673597190 - // - // the results are correct no matter which community we fetch these for, since it basically - // covers the "worst case" of the whole page consisting of posts from one community - // but using the largest community decreases the pagination-frame so make the real query more - // efficient. - use lemmy_db_schema::schema::community_aggregates::dsl::{ - community_aggregates, - community_id, - users_active_month, - }; - let (limit, offset) = limit_and_offset(self.page, self.limit)?; - if offset != 0 && self.page_after.is_some() { - return Err(Error::QueryBuilderError( - "legacy pagination cannot be combined with v2 pagination".into(), - )); - } - let self_person_id = self.local_user.expect("part of the above if").person_id; - let largest_subscribed = { - let conn = &mut get_conn(pool).await?; - action_query(community_actions::followed) - .filter(community_actions::person_id.eq(self_person_id)) - .inner_join(community_aggregates.on(community_id.eq(community_actions::community_id))) - .order_by(users_active_month.desc()) - .select(community_id) - .limit(1) - .get_result::(conn) - .await - .optional()? - }; - let Some(largest_subscribed) = largest_subscribed else { - // nothing subscribed to? no posts - return Ok(None); - }; - - let mut v = queries() - .list( - pool, - ( - PostQuery { - community_id: Some(largest_subscribed), - community_id_just_for_prefetch: true, - ..self.clone() - }, - site, - ), - ) - .await?; - // take last element of array. if this query returned less than LIMIT elements, - // the heuristic is invalid since we can't guarantee the full query will return >= LIMIT results - // (return original query) - if (v.len() as i64) < limit { - Ok(Some(self.clone())) - } else { - let item = if self.page_back.unwrap_or_default() { - // for backward pagination, get first element instead - v.into_iter().next() - } else { - v.pop() - }; - let limit_cursor = Some(item.expect("else case").counts); - Ok(Some(PostQuery { - page_before_or_equal: limit_cursor, - ..self.clone() - })) - } - } - - pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result, Error> { - if self.listing_type == Some(ListingType::Subscribed) - && self.community_id.is_none() - && self.local_user.is_some() - && self.page_before_or_equal.is_none() - { - if let Some(query) = self - .prefetch_upper_bound_for_page_before(site, pool) - .await? - { - queries().list(pool, (query, site)).await - } else { - Ok(vec![]) - } - } else { - queries().list(pool, (self, site)).await - } } } diff --git a/crates/db_views/src/private_message/private_message_view.rs b/crates/db_views/src/private_message/private_message_view.rs index 2345e7466..799fdde82 100644 --- a/crates/db_views/src/private_message/private_message_view.rs +++ b/crates/db_views/src/private_message/private_message_view.rs @@ -1,41 +1,56 @@ use crate::structs::PrivateMessageView; -use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel::{ + result::Error, + BoolExpressionMethods, + ExpressionMethods, + JoinOnDsl, + QueryDsl, + SelectableHelper, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases, newtypes::PrivateMessageId, schema::{instance_actions, person, person_actions, private_message}, - utils::{actions, get_conn, DbPool}, + utils::{get_conn, DbPool}, }; impl PrivateMessageView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins() -> _ { + let recipient_id = aliases::person1.field(person::id); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(private_message::creator_id) + .and(person_actions::person_id.eq(recipient_id)), + ); + + let instance_actions_join = instance_actions::table.on( + instance_actions::instance_id + .eq(person::instance_id) + .and(instance_actions::person_id.eq(recipient_id)), + ); + + let creator_join = person::table.on(private_message::creator_id.eq(person::id)); + + let recipient_join = aliases::person1.on(private_message::recipient_id.eq(recipient_id)); + + private_message::table + .inner_join(creator_join) + .inner_join(recipient_join) + .left_join(person_actions_join) + .left_join(instance_actions_join) + } + pub async fn read( pool: &mut DbPool<'_>, private_message_id: PrivateMessageId, ) -> Result { let conn = &mut get_conn(pool).await?; - - private_message::table - .find(private_message_id) - .inner_join(person::table.on(private_message::creator_id.eq(person::id))) - .inner_join( - aliases::person1.on(private_message::recipient_id.eq(aliases::person1.field(person::id))), - ) - .left_join(actions( - person_actions::table, - Some(aliases::person1.field(person::id)), - private_message::creator_id, - )) - .left_join(actions( - instance_actions::table, - Some(aliases::person1.field(person::id)), - person::instance_id, - )) - .select(( - private_message::all_columns, - person::all_columns, - aliases::person1.fields(person::all_columns), - )) + Self::joins() + .filter(private_message::id.eq(private_message_id)) + .select(Self::as_select()) .first(conn) .await } diff --git a/crates/db_views/src/registration_applications/registration_application_view.rs b/crates/db_views/src/registration_applications/registration_application_view.rs index cdb6af61d..f10b085c4 100644 --- a/crates/db_views/src/registration_applications/registration_application_view.rs +++ b/crates/db_views/src/registration_applications/registration_application_view.rs @@ -1,119 +1,69 @@ use crate::structs::RegistrationApplicationView; use diesel::{ dsl::count, - pg::Pg, result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl, + SelectableHelper, }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases, newtypes::{PersonId, RegistrationApplicationId}, schema::{local_user, person, registration_application}, - utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + source::registration_application::RegistrationApplication, + utils::{get_conn, limit_and_offset, DbPool}, }; -enum ReadBy { - Id(RegistrationApplicationId), - Person(PersonId), -} - -fn queries<'a>() -> Queries< - impl ReadFn<'a, RegistrationApplicationView, ReadBy>, - impl ListFn<'a, RegistrationApplicationView, RegistrationApplicationQuery>, -> { - let all_joins = |query: registration_application::BoxedQuery<'a, Pg>| { - query +impl RegistrationApplicationView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins() -> _ { + registration_application::table .inner_join(local_user::table.on(registration_application::local_user_id.eq(local_user::id))) .inner_join(person::table.on(local_user::person_id.eq(person::id))) .left_join( aliases::person1 .on(registration_application::admin_id.eq(aliases::person1.field(person::id).nullable())), ) - .order_by(registration_application::published.desc()) - .select(( - registration_application::all_columns, - local_user::all_columns, - person::all_columns, - aliases::person1.fields(person::all_columns).nullable(), - )) - }; + } - let read = move |mut conn: DbConn<'a>, search: ReadBy| async move { - let mut query = all_joins(registration_application::table.into_boxed()); - - query = match search { - ReadBy::Id(id) => query.filter(registration_application::id.eq(id)), - ReadBy::Person(person_id) => query.filter(person::id.eq(person_id)), - }; - - query.first(&mut conn).await - }; - - let list = move |mut conn: DbConn<'a>, o: RegistrationApplicationQuery| async move { - let mut query = all_joins(registration_application::table.into_boxed()); - - // If viewing all applications, order by newest, but if viewing unresolved only, show the oldest - // first (FIFO) - if o.unread_only { - query = query - .filter(registration_application::admin_id.is_null()) - .order_by(registration_application::published.asc()); - } else { - query = query.order_by(registration_application::published.desc()); - } - - if o.verified_email_only { - query = query.filter(local_user::email_verified.eq(true)) - } - - let (limit, offset) = limit_and_offset(o.page, o.limit)?; - - query = query.limit(limit).offset(offset); - - query.load::(&mut conn).await - }; - - Queries::new(read, list) -} - -impl RegistrationApplicationView { pub async fn read(pool: &mut DbPool<'_>, id: RegistrationApplicationId) -> Result { - queries().read(pool, ReadBy::Id(id)).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(registration_application::id.eq(id)) + .select(Self::as_select()) + .first(conn) + .await } pub async fn read_by_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { - queries().read(pool, ReadBy::Person(person_id)).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(person::id.eq(person_id)) + .select(Self::as_select()) + .first(conn) + .await } + /// Returns the current unread registration_application count pub async fn get_unread_count( pool: &mut DbPool<'_>, verified_email_only: bool, ) -> Result { let conn = &mut get_conn(pool).await?; - let person_alias_1 = diesel::alias!(person as person1); - let mut query = registration_application::table - .inner_join(local_user::table.on(registration_application::local_user_id.eq(local_user::id))) - .inner_join(person::table.on(local_user::person_id.eq(person::id))) - .left_join( - person_alias_1 - .on(registration_application::admin_id.eq(person_alias_1.field(person::id).nullable())), - ) - .filter(registration_application::admin_id.is_null()) + let mut query = Self::joins() + .filter(RegistrationApplication::is_unread()) + .select(count(registration_application::id)) .into_boxed(); if verified_email_only { query = query.filter(local_user::email_verified.eq(true)) } - query - .select(count(registration_application::id)) - .first::(conn) - .await + query.first::(conn).await } } @@ -130,7 +80,32 @@ impl RegistrationApplicationQuery { self, pool: &mut DbPool<'_>, ) -> Result, Error> { - queries().list(pool, self).await + let conn = &mut get_conn(pool).await?; + let o = self; + + let mut query = RegistrationApplicationView::joins() + .select(RegistrationApplicationView::as_select()) + .into_boxed(); + + if o.unread_only { + query = query + .filter(RegistrationApplication::is_unread()) + .order_by(registration_application::published.asc()); + } else { + query = query.order_by(registration_application::published.desc()); + } + + if o.verified_email_only { + query = query.filter(local_user::email_verified.eq(true)) + } + + let (limit, offset) = limit_and_offset(o.page, o.limit)?; + + query + .limit(limit) + .offset(offset) + .load::(conn) + .await } } diff --git a/crates/db_views/src/reports/comment_report_view.rs b/crates/db_views/src/reports/comment_report_view.rs index 6154b9b56..a2fa22a26 100644 --- a/crates/db_views/src/reports/comment_report_view.rs +++ b/crates/db_views/src/reports/comment_report_view.rs @@ -25,10 +25,78 @@ use lemmy_db_schema::{ post, }, source::community::CommunityFollower, - utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool}, + utils::{functions::coalesce, get_conn, DbPool}, }; impl CommentReportView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(my_person_id: PersonId) -> _ { + let recipient_id = aliases::person1.field(person::id); + let resolver_id = aliases::person2.field(person::id); + + let post_join = post::table.on(comment::post_id.eq(post::id)); + + let community_join = community::table.on(post::community_id.eq(community::id)); + + let report_creator_join = person::table.on(comment_report::creator_id.eq(person::id)); + + let local_user_join = local_user::table.on( + comment::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ); + + let comment_creator_join = aliases::person1.on(comment::creator_id.eq(recipient_id)); + + let comment_aggregates_join = + comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)); + + let comment_actions_join = comment_actions::table.on( + comment_actions::comment_id + .eq(comment_report::comment_id) + .and(comment_actions::person_id.eq(my_person_id)), + ); + + let resolver_join = aliases::person2.on(comment_report::resolver_id.eq(resolver_id.nullable())); + + let creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(comment::creator_id), + ), + ); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(comment::creator_id) + .and(person_actions::person_id.eq(my_person_id)), + ); + + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(post::community_id) + .and(community_actions::person_id.eq(my_person_id)), + ); + + comment_report::table + .inner_join(comment::table) + .inner_join(post_join) + .inner_join(community_join) + .inner_join(report_creator_join) + .inner_join(comment_creator_join) + .inner_join(comment_aggregates_join) + .left_join(comment_actions_join) + .left_join(resolver_join) + .left_join(creator_community_actions_join) + .left_join(local_user_join) + .left_join(person_actions_join) + .left_join(community_actions_join) + } + /// returns the CommentReportView for the provided report_id /// /// * `report_id` - the report id to obtain @@ -38,47 +106,8 @@ impl CommentReportView { my_person_id: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - comment_report::table - .find(report_id) - .inner_join(comment::table) - .inner_join(post::table.on(comment::post_id.eq(post::id))) - .inner_join(community::table.on(post::community_id.eq(community::id))) - .inner_join(person::table.on(comment_report::creator_id.eq(person::id))) - .inner_join(aliases::person1.on(comment::creator_id.eq(aliases::person1.field(person::id)))) - .inner_join( - comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), - ) - .left_join(actions( - comment_actions::table, - Some(my_person_id), - comment_report::comment_id, - )) - .left_join( - aliases::person2 - .on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), - ) - .left_join(actions_alias( - creator_community_actions, - comment::creator_id, - post::community_id, - )) - .left_join( - local_user::table.on( - comment::creator_id - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ) - .left_join(actions( - person_actions::table, - Some(my_person_id), - comment::creator_id, - )) - .left_join(actions( - community_actions::table, - Some(my_person_id), - post::community_id, - )) + Self::joins(my_person_id) + .filter(comment_report::id.eq(report_id)) .select(( comment_report::all_columns, comment::all_columns, diff --git a/crates/db_views/src/reports/post_report_view.rs b/crates/db_views/src/reports/post_report_view.rs index 4c7fd676c..100703f06 100644 --- a/crates/db_views/src/reports/post_report_view.rs +++ b/crates/db_views/src/reports/post_report_view.rs @@ -23,10 +23,75 @@ use lemmy_db_schema::{ post_report, }, source::community::CommunityFollower, - utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool}, + utils::{functions::coalesce, get_conn, DbPool}, }; impl PostReportView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins(my_person_id: PersonId) -> _ { + let recipient_id = aliases::person1.field(person::id); + let resolver_id = aliases::person2.field(person::id); + + let community_join = community::table.on(post::community_id.eq(community::id)); + + let report_creator_join = person::table.on(post_report::creator_id.eq(person::id)); + + let post_creator_join = aliases::person1.on(post::creator_id.eq(recipient_id)); + + let creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(post::creator_id), + ), + ); + + let community_actions_join = community_actions::table.on( + community_actions::community_id + .eq(post::community_id) + .and(community_actions::person_id.eq(my_person_id)), + ); + + let local_user_join = local_user::table.on( + post::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ); + + let post_actions_join = post_actions::table.on( + post_actions::post_id + .eq(post::id) + .and(post_actions::person_id.eq(my_person_id)), + ); + + let person_actions_join = person_actions::table.on( + person_actions::target_id + .eq(post::creator_id) + .and(person_actions::person_id.eq(my_person_id)), + ); + + let post_aggregates_join = + post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)); + + let resolver_join = aliases::person2.on(post_report::resolver_id.eq(resolver_id.nullable())); + + post_report::table + .inner_join(post::table) + .inner_join(community_join) + .inner_join(report_creator_join) + .inner_join(post_creator_join) + .left_join(creator_community_actions_join) + .left_join(community_actions_join) + .left_join(local_user_join) + .left_join(post_actions_join) + .left_join(person_actions_join) + .inner_join(post_aggregates_join) + .left_join(resolver_join) + } + /// returns the PostReportView for the provided report_id /// /// * `report_id` - the report id to obtain @@ -37,40 +102,8 @@ impl PostReportView { ) -> Result { let conn = &mut get_conn(pool).await?; - post_report::table - .find(report_id) - .inner_join(post::table) - .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(aliases::person1.on(post::creator_id.eq(aliases::person1.field(person::id)))) - .left_join(actions_alias( - creator_community_actions, - post::creator_id, - post::community_id, - )) - .left_join(actions( - community_actions::table, - Some(my_person_id), - post::community_id, - )) - .left_join( - local_user::table.on( - post::creator_id - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ) - .left_join(actions(post_actions::table, Some(my_person_id), post::id)) - .left_join(actions( - person_actions::table, - Some(my_person_id), - post::creator_id, - )) - .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) - .left_join( - aliases::person2 - .on(post_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), - ) + Self::joins(my_person_id) + .filter(post_report::id.eq(report_id)) .select(( post_report::all_columns, post::all_columns, diff --git a/crates/db_views/src/site/custom_emoji_view.rs b/crates/db_views/src/site/custom_emoji_view.rs index 606e807e9..a47e06275 100644 --- a/crates/db_views/src/site/custom_emoji_view.rs +++ b/crates/db_views/src/site/custom_emoji_view.rs @@ -1,5 +1,12 @@ use crate::structs::CustomEmojiView; -use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl}; +use diesel::{ + dsl::Nullable, + result::Error, + ExpressionMethods, + JoinOnDsl, + NullableExpressionMethods, + QueryDsl, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::CustomEmojiId, @@ -9,20 +16,33 @@ use lemmy_db_schema::{ }; use std::collections::HashMap; +type SelectionType = ( + ::AllColumns, + Nullable<::AllColumns>, +); + +fn selection() -> SelectionType { + ( + custom_emoji::all_columns, + custom_emoji_keyword::all_columns.nullable(), // (or all the columns if you want) + ) +} type CustomEmojiTuple = (CustomEmoji, Option); +// TODO this type is a mess, it should not be using vectors in a view. impl CustomEmojiView { + #[diesel::dsl::auto_type(no_type_alias)] + fn joins() -> _ { + custom_emoji::table.left_join( + custom_emoji_keyword::table.on(custom_emoji_keyword::custom_emoji_id.eq(custom_emoji::id)), + ) + } + pub async fn get(pool: &mut DbPool<'_>, emoji_id: CustomEmojiId) -> Result { let conn = &mut get_conn(pool).await?; - let emojis = custom_emoji::table - .find(emoji_id) - .left_join( - custom_emoji_keyword::table.on(custom_emoji_keyword::custom_emoji_id.eq(custom_emoji::id)), - ) - .select(( - custom_emoji::all_columns, - custom_emoji_keyword::all_columns.nullable(), // (or all the columns if you want) - )) + let emojis = Self::joins() + .filter(custom_emoji::id.eq(emoji_id)) + .select(selection()) .load::(conn) .await?; if let Some(emoji) = CustomEmojiView::from_tuple_to_vec(emojis) @@ -44,12 +64,7 @@ impl CustomEmojiView { ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - let mut query = custom_emoji::table - .left_join( - custom_emoji_keyword::table.on(custom_emoji_keyword::custom_emoji_id.eq(custom_emoji::id)), - ) - .order(custom_emoji::category) - .into_boxed(); + let mut query = Self::joins().into_boxed(); if !ignore_page_limits { let (limit, offset) = limit_and_offset(page, limit)?; @@ -60,13 +75,10 @@ impl CustomEmojiView { query = query.filter(custom_emoji::category.eq(category)) } - query = query.then_order_by(custom_emoji::id); - let emojis = query - .select(( - custom_emoji::all_columns, - custom_emoji_keyword::all_columns.nullable(), // (or all the columns if you want) - )) + .select(selection()) + .order(custom_emoji::category) + .then_order_by(custom_emoji::id) .load::(conn) .await?; diff --git a/crates/db_views/src/site/local_image_view.rs b/crates/db_views/src/site/local_image_view.rs index 7b5b97095..3d342528c 100644 --- a/crates/db_views/src/site/local_image_view.rs +++ b/crates/db_views/src/site/local_image_view.rs @@ -1,5 +1,5 @@ use crate::structs::LocalImageView; -use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; +use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl, SelectableHelper}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::LocalUserId, @@ -8,31 +8,11 @@ use lemmy_db_schema::{ }; impl LocalImageView { - async fn get_all_helper( - pool: &mut DbPool<'_>, - user_id: Option, - page: Option, - limit: Option, - ignore_page_limits: bool, - ) -> Result, Error> { - let conn = &mut get_conn(pool).await?; - let mut query = local_image::table + #[diesel::dsl::auto_type(no_type_alias)] + fn joins() -> _ { + local_image::table .inner_join(local_user::table) .inner_join(person::table.on(local_user::person_id.eq(person::id))) - .select((local_image::all_columns, person::all_columns)) - .order_by(local_image::published.desc()) - .into_boxed(); - - if let Some(user_id) = user_id { - query = query.filter(local_image::local_user_id.eq(user_id)) - } - - if !ignore_page_limits { - let (limit, offset) = limit_and_offset(page, limit)?; - query = query.limit(limit).offset(offset); - } - - query.load::(conn).await } pub async fn get_all_paged_by_local_user_id( @@ -41,14 +21,28 @@ impl LocalImageView { page: Option, limit: Option, ) -> Result, Error> { - Self::get_all_helper(pool, Some(user_id), page, limit, false).await + let conn = &mut get_conn(pool).await?; + let (limit, offset) = limit_and_offset(page, limit)?; + + Self::joins() + .filter(local_image::local_user_id.eq(user_id)) + .select(Self::as_select()) + .limit(limit) + .offset(offset) + .load::(conn) + .await } pub async fn get_all_by_local_user_id( pool: &mut DbPool<'_>, user_id: LocalUserId, ) -> Result, Error> { - Self::get_all_helper(pool, Some(user_id), None, None, true).await + let conn = &mut get_conn(pool).await?; + Self::joins() + .filter(local_image::local_user_id.eq(user_id)) + .select(Self::as_select()) + .load::(conn) + .await } pub async fn get_all( @@ -56,6 +50,13 @@ impl LocalImageView { page: Option, limit: Option, ) -> Result, Error> { - Self::get_all_helper(pool, None, page, limit, false).await + let conn = &mut get_conn(pool).await?; + let (limit, offset) = limit_and_offset(page, limit)?; + Self::joins() + .select(Self::as_select()) + .limit(limit) + .offset(offset) + .load::(conn) + .await } } diff --git a/crates/db_views/src/site/site_view.rs b/crates/db_views/src/site/site_view.rs index ed9aeb498..ecf730e88 100644 --- a/crates/db_views/src/site/site_view.rs +++ b/crates/db_views/src/site/site_view.rs @@ -1,5 +1,5 @@ use crate::structs::SiteView; -use diesel::{ExpressionMethods, JoinOnDsl, OptionalExtension, QueryDsl}; +use diesel::{ExpressionMethods, JoinOnDsl, OptionalExtension, QueryDsl, SelectableHelper}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ schema::{local_site, local_site_rate_limit, site, site_aggregates}, @@ -17,12 +17,7 @@ impl SiteView { local_site_rate_limit::table.on(local_site::id.eq(local_site_rate_limit::local_site_id)), ) .inner_join(site_aggregates::table) - .select(( - site::all_columns, - local_site::all_columns, - local_site_rate_limit::all_columns, - site_aggregates::all_columns, - )) + .select(Self::as_select()) .first(conn) .await .optional()? diff --git a/crates/db_views/src/site/vote_view.rs b/crates/db_views/src/site/vote_view.rs index 827cd3cc9..dac785753 100644 --- a/crates/db_views/src/site/vote_view.rs +++ b/crates/db_views/src/site/vote_view.rs @@ -1,11 +1,18 @@ use crate::structs::VoteView; -use diesel::{result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; +use diesel::{ + result::Error, + BoolExpressionMethods, + ExpressionMethods, + JoinOnDsl, + NullableExpressionMethods, + QueryDsl, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::creator_community_actions, newtypes::{CommentId, PostId}, schema::{comment, comment_actions, community_actions, person, post, post_actions}, - utils::{action_query, actions_alias, get_conn, limit_and_offset, DbPool}, + utils::{get_conn, limit_and_offset, DbPool}, }; impl VoteView { @@ -18,14 +25,22 @@ impl VoteView { let conn = &mut get_conn(pool).await?; let (limit, offset) = limit_and_offset(page, limit)?; - action_query(post_actions::like_score) + let creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(post_actions::person_id), + ), + ); + + post_actions::table + .filter(post_actions::like_score.is_not_null()) .inner_join(person::table) .inner_join(post::table) - .left_join(actions_alias( - creator_community_actions, - post_actions::person_id, - post::community_id, - )) + .left_join(creator_community_actions_join) .filter(post_actions::post_id.eq(post_id)) .select(( person::all_columns, @@ -51,14 +66,22 @@ impl VoteView { let conn = &mut get_conn(pool).await?; let (limit, offset) = limit_and_offset(page, limit)?; - action_query(comment_actions::like_score) + let creator_community_actions_join = creator_community_actions.on( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id) + .and( + creator_community_actions + .field(community_actions::person_id) + .eq(comment_actions::person_id), + ), + ); + + comment_actions::table + .filter(comment_actions::like_score.is_not_null()) .inner_join(person::table) .inner_join(comment::table.inner_join(post::table)) - .left_join(actions_alias( - creator_community_actions, - comment_actions::person_id, - post::community_id, - )) + .left_join(creator_community_actions_join) .filter(comment_actions::comment_id.eq(comment_id)) .select(( person::all_columns, diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index 09a868be9..c224162c7 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -1,7 +1,17 @@ #[cfg(feature = "full")] -use diesel::Queryable; -#[cfg(feature = "full")] -use diesel::{deserialize::FromSqlRow, expression::AsExpression, sql_types}; +use diesel::{ + deserialize::FromSqlRow, + dsl::exists, + dsl::Nullable, + expression::AsExpression, + sql_types, + BoolExpressionMethods, + ExpressionMethods, + NullableExpressionMethods, + QueryDsl, + Queryable, + Selectable, +}; use lemmy_db_schema::{ aggregates::structs::{ CommentAggregates, @@ -60,6 +70,14 @@ use lemmy_db_schema::{ }, SubscribedType, }; +#[cfg(feature = "full")] +use lemmy_db_schema::{ + aliases::{creator_community_actions, person1}, + schema::{comment, comment_actions, community_actions, local_user, person, person_actions}, + source::community::CommunityFollower, + utils::functions::coalesce, + Person1AliasAllColumnsTuple, +}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -93,24 +111,90 @@ pub struct CommentReportView { #[skip_serializing_none] #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A comment view. pub struct CommentView { + #[cfg_attr(feature = "full", diesel(embed))] pub comment: Comment, + #[cfg_attr(feature = "full", diesel(embed))] pub creator: Person, + #[cfg_attr(feature = "full", diesel(embed))] pub post: Post, + #[cfg_attr(feature = "full", diesel(embed))] pub community: Community, + #[cfg_attr(feature = "full", diesel(embed))] pub counts: CommentAggregates, + #[cfg_attr(feature = "full", + diesel( + select_expression = + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_not_null() + ) + )] pub creator_banned_from_community: bool, + #[cfg_attr(feature = "full", + diesel( + select_expression = + community_actions::received_ban.nullable().is_not_null() + ) + )] pub banned_from_community: bool, + #[cfg_attr(feature = "full", + diesel( + select_expression = + creator_community_actions + .field(community_actions::became_moderator) + .nullable() + .is_not_null() + ) + )] pub creator_is_moderator: bool, + #[cfg_attr(feature = "full", + diesel( + select_expression = + exists( + local_user::table.filter( + comment::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)) + ) + ) + ) + )] pub creator_is_admin: bool, + #[cfg_attr(feature = "full", + diesel( + select_expression_type = Nullable, + select_expression = + CommunityFollower::select_subscribed_type(), + ) + )] pub subscribed: SubscribedType, + #[cfg_attr(feature = "full", + diesel( + select_expression = + comment_actions::saved.nullable().is_not_null() + ) + )] pub saved: bool, + #[cfg_attr(feature = "full", + diesel( + select_expression = + person_actions::blocked.nullable().is_not_null() + ) + )] pub creator_blocked: bool, #[cfg_attr(feature = "full", ts(optional))] + #[cfg_attr(feature = "full", + diesel( + select_expression = + comment_actions::like_score.nullable() + ) + )] pub my_vote: Option, } @@ -152,14 +236,18 @@ pub struct CommunityReportView { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A local user view. pub struct LocalUserView { + #[cfg_attr(feature = "full", diesel(embed))] pub local_user: LocalUser, + #[cfg_attr(feature = "full", diesel(embed))] pub local_user_vote_display_mode: LocalUserVoteDisplayMode, + #[cfg_attr(feature = "full", diesel(embed))] pub person: Person, + #[cfg_attr(feature = "full", diesel(embed))] pub counts: PersonAggregates, } @@ -263,27 +351,40 @@ pub struct PrivateMessageReportView { #[skip_serializing_none] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A registration application view. pub struct RegistrationApplicationView { + #[cfg_attr(feature = "full", diesel(embed))] pub registration_application: RegistrationApplication, + #[cfg_attr(feature = "full", diesel(embed))] pub creator_local_user: LocalUser, + #[cfg_attr(feature = "full", diesel(embed))] pub creator: Person, #[cfg_attr(feature = "full", ts(optional))] + #[cfg_attr(feature = "full", + diesel( + select_expression_type = Nullable, + select_expression = person1.fields(person::all_columns).nullable() + ) + )] pub admin: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A site view. pub struct SiteView { + #[cfg_attr(feature = "full", diesel(embed))] pub site: Site, + #[cfg_attr(feature = "full", diesel(embed))] pub local_site: LocalSite, + #[cfg_attr(feature = "full", diesel(embed))] pub local_site_rate_limit: LocalSiteRateLimit, + #[cfg_attr(feature = "full", diesel(embed))] pub counts: SiteAggregates, } @@ -311,12 +412,14 @@ pub struct VoteView { #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A local image view. pub struct LocalImageView { + #[cfg_attr(feature = "full", diesel(embed))] pub local_image: LocalImage, + #[cfg_attr(feature = "full", diesel(embed))] pub person: Person, } @@ -412,44 +515,68 @@ pub enum PersonContentCombinedView { } #[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A community follower. pub struct CommunityFollowerView { + #[cfg_attr(feature = "full", diesel(embed))] pub community: Community, + #[cfg_attr(feature = "full", diesel(embed))] pub follower: Person, } #[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A community moderator. pub struct CommunityModeratorView { + #[cfg_attr(feature = "full", diesel(embed))] pub community: Community, + #[cfg_attr(feature = "full", diesel(embed))] pub moderator: Person, } #[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(Queryable))] +#[cfg_attr(feature = "full", derive(Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] /// A community person ban. pub struct CommunityPersonBanView { + #[cfg_attr(feature = "full", diesel(embed))] pub community: Community, + #[cfg_attr(feature = "full", diesel(embed))] pub person: Person, } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A community view. pub struct CommunityView { + #[cfg_attr(feature = "full", diesel(embed))] pub community: Community, + #[cfg_attr(feature = "full", + diesel( + select_expression_type = Nullable, + select_expression = CommunityFollower::select_subscribed_type() + ) + )] pub subscribed: SubscribedType, + #[cfg_attr(feature = "full", + diesel( + select_expression = community_actions::blocked.nullable().is_not_null() + ) + )] pub blocked: bool, + #[cfg_attr(feature = "full", diesel(embed))] pub counts: CommunityAggregates, + #[cfg_attr(feature = "full", + diesel( + select_expression = community_actions::received_ban.nullable().is_not_null() + ) + )] pub banned_from_community: bool, } @@ -458,28 +585,20 @@ pub struct CommunityView { #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] pub enum CommunitySortType { + ActiveSixMonths, #[default] - Active, + ActiveMonthly, + ActiveWeekly, + ActiveDaily, Hot, New, Old, - TopDay, - TopWeek, - TopMonth, - TopYear, - TopAll, - MostComments, - NewComments, - TopHour, - TopSixHour, - TopTwelveHour, - TopThreeMonths, - TopSixMonths, - TopNineMonths, - Controversial, - Scaled, NameAsc, NameDesc, + Comments, + Posts, + Subscribers, + SubscribersLocal, } #[skip_serializing_none] @@ -563,13 +682,21 @@ pub struct CommentReplyView { } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A person view. pub struct PersonView { + #[cfg_attr(feature = "full", diesel(embed))] pub person: Person, + #[cfg_attr(feature = "full", diesel(embed))] pub counts: PersonAggregates, + #[cfg_attr(feature = "full", + diesel( + select_expression_type = coalesce, bool>, + select_expression = coalesce(local_user::admin.nullable(), false) + ) + )] pub is_admin: bool, } @@ -585,13 +712,21 @@ pub struct PendingFollow { } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A private message view. pub struct PrivateMessageView { + #[cfg_attr(feature = "full", diesel(embed))] pub private_message: PrivateMessage, + #[cfg_attr(feature = "full", diesel(embed))] pub creator: Person, + #[cfg_attr(feature = "full", + diesel( + select_expression_type = Person1AliasAllColumnsTuple, + select_expression = person1.fields(person::all_columns) + ) + )] pub recipient: Person, } @@ -888,36 +1023,64 @@ pub struct AdminAllowInstanceView { pub struct ModlogCombinedPaginationCursor(pub String); #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(Queryable))] +#[cfg_attr(feature = "full", derive(Queryable, Selectable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] /// A combined modlog view pub struct ModlogCombinedViewInternal { // Specific + #[cfg_attr(feature = "full", diesel(embed))] pub admin_allow_instance: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub admin_block_instance: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub admin_purge_comment: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub admin_purge_community: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub admin_purge_person: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub admin_purge_post: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_add: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_add_community: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_ban: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_ban_from_community: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_feature_post: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_hide_community: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_lock_post: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_remove_comment: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_remove_community: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_remove_post: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub mod_transfer_community: Option, // Specific fields // Shared + #[cfg_attr(feature = "full", diesel(embed))] pub moderator: Option, + #[cfg_attr(feature = "full", + diesel( + select_expression_type = Nullable, + select_expression = person1.fields(person::all_columns).nullable() + ) + )] pub other_person: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub instance: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub community: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub post: Option, + #[cfg_attr(feature = "full", diesel(embed))] pub comment: Option, } diff --git a/crates/routes/src/utils/scheduled_tasks.rs b/crates/routes/src/utils/scheduled_tasks.rs index 83463c344..3d50116a4 100644 --- a/crates/routes/src/utils/scheduled_tasks.rs +++ b/crates/routes/src/utils/scheduled_tasks.rs @@ -39,15 +39,7 @@ use lemmy_db_schema::{ post::{Post, PostUpdateForm}, }, traits::Crud, - utils::{ - find_action, - functions::coalesce, - get_conn, - now, - uplete, - DbPool, - DELETED_REPLACEMENT_TEXT, - }, + utils::{functions::coalesce, get_conn, now, uplete, DbPool, DELETED_REPLACEMENT_TEXT}, }; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use reqwest_middleware::ClientWithMiddleware; @@ -435,6 +427,10 @@ async fn publish_scheduled_posts(context: &Data) -> LemmyResult<() let pool = &mut context.pool(); let mut conn = get_conn(pool).await?; + let not_banned_action = community_actions::table + .find((person::id, community::id)) + .filter(community_actions::received_ban.is_not_null()); + let scheduled_posts: Vec<_> = post::table .inner_join(community::table) .inner_join(person::table) @@ -446,10 +442,7 @@ async fn publish_scheduled_posts(context: &Data) -> LemmyResult<() .filter(not(person::banned.or(person::deleted))) .filter(not(community::removed.or(community::deleted))) // ensure that user isnt banned from community - .filter(not(exists(find_action( - community_actions::received_ban, - (person::id, community::id), - )))) + .filter(not(exists(not_banned_action))) .select((post::all_columns, community::all_columns)) .get_results::<(Post, Community)>(&mut conn) .await?; diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index 806a4f86f..fe764ed8c 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -1,6 +1,6 @@ use cfg_if::cfg_if; use serde::{Deserialize, Serialize}; -use std::{backtrace::Backtrace, fmt::Debug}; +use std::fmt::Debug; use strum::{Display, EnumIter}; #[derive(Display, Debug, Serialize, Deserialize, Clone, PartialEq, Eq, EnumIter, Hash)] @@ -192,7 +192,7 @@ pub enum FederationError { cfg_if! { if #[cfg(feature = "full")] { - use std::fmt; + use std::{fmt, backtrace::Backtrace}; pub type LemmyResult = Result; pub struct LemmyError {