Converting joins, filters, and selects to common functions for DB views. (#5359)

* Renaming person_mention to person_comment_mention.

* Finishing up post body mentions.

* Combined tables try 2

* Finishing up combined report table.

* Fix ts optionals.

* Adding tests, triggers, and history updates for report_combined.

* Adding profile.

* Add cursor pagination to report_combined view (#5244)

* add pagination cursor

* store timestamp instead of id in cursor (partial)

* Revert "store timestamp instead of id in cursor (partial)"

This reverts commit 89359dde4b.

* use paginated query builder

* Fixing migration and paged API.

* Using dullbananas trigger procedure

* Removing pointless list routes, reorganizing tests.

* Fixing column XOR check.

* Forgot to remove list report actions.

* Cleanup.

* Use internal tagging.

* Fixing api tests.

* Adding a few indexes.

* Fixing migration name.

* Fixing unique constraints.

* Addressing PR comments.

* Start working on profile combined

* Adding views and replaceable schema.

* A few changes to profile view.

- Separating the profile fetch from its combined content fetch.
- Starting to separate saved_only into its own combined view.

* Finishing up combined person_saved and person_content.

* Fixing api tests.

* Moving to api-v4 routes.

* Fixing imports.

* Update crates/db_views/src/report_combined_view.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Update crates/db_views/src/report_combined_view.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Update crates/db_views/src/report_combined_view.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Update migrations/2024-12-02-181601_add_report_combined_table/up.sql

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Update migrations/2024-12-02-181601_add_report_combined_table/up.sql

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Fixing import and fmt.

* Fixing null types in postgres.

* Comment out err.

* Fixing TS issues.

* Adding types, fixing allow and blocklist crud.

* Starting to work on combined views.

* Using dullbananas trigger procedure

* Adding the full combined view queries.

* Adding tests.

* taplo fmt.

* Upgrading package.json deps.

* Updating pnpm

* Most of the bulk work done, need to add tests yet.

* Finishing up inbox.

* Using assert_length

* Fixing sql_format.

* Running fmt.

* Fixing cargo shear.

* Fixing clippy.

* Addressing PR comments.

* Starting to work on search combined.

* Fix

* Removing serialization

* Removing serialization

* Moving db_views_actor and _moderator into db_views.

- This is necessary because the combined views use both, and that
  separation was arbitrary to begin with. db_schema has no such crate
  separation.

* Adding search combined view, need to write tests yet.

* Filters done, working on tests.

* Adding tests for person, post, and community.

* Finishing up tests.

* Fixing duped trigger.

* Remove saved_only test.

* Remove pointless post_tags types.

* Remove pointless index.

* Changing published to saved for person_saved_combined.

* Removing comment.

* Renaming modlog when_ columns to published.

- Fixes #5312

* Adding strum and simplifying imports.

* Avoiding clone in map_to_enum

* Changing modded_person to other_person.

* Update crates/db_views_moderator/src/modlog_combined_view.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Update crates/db_views_moderator/src/modlog_combined_view.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Update crates/db_views_moderator/src/modlog_combined_view.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Addressing PR comments.

* Fixing split.

* Revert "Adding strum and simplifying imports."

This reverts commit 15f1671107.

* Running fmt.

* Using assert + matches instead of filter_map.

* Adding listPersonContent check.

* Updating lemmy-js-client

* Fixing mark all as read route, changing mark read to SuccessResponse.

* Adding post body mention api test, fixing api tests.

* Fixing route locations, and api tests.

* Formatting sql.

* Formatting sql 2.

* Fixing search result, running clippy.

* Fixing ts_option.

* Adding search_combined.score column, and DB triggers.

* Fixing API tests.

* Adding an index for score.

* Using next_back instead of last.

* Converting local_user_view to use diesel auto_type.

* Use AllColumns as suggested by weiznich to simplify Type.

* Registration application view.

* Person view simplifying.

* Community view.

* Community moderator view.

* Community view.

* Community follower view.

* Community moderator view.

* Person view.

* Custom emoji view.

* Local image view.

* Removing strum from db_views.

* Update crates/db_schema/src/newtypes.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Adding an action_utils.

* Avoiding inner joins for up.sql

* Adding person_aggregates.published column.

* Remove unused private message view.

* Implementing Selectable for DB views, moving joins inside impl.

* Adding a few more.

* Fixing imports.

* fmt.

* Post view.

* Comment view.

* Inbox combined view.

* Modlog combined view.

* The rest of the combined tables.

* Finishing up.

* Fixing shear.

* Fixing cargo.toml.

* Use 1.81 image.

* Fix api common.

* Fixing merge issues.

---------

Co-authored-by: dullbananas <dull.bananas0@gmail.com>
This commit is contained in:
Dessalines 2025-02-03 19:47:48 -05:00 committed by GitHub
parent 1d0a47460d
commit e654462405
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 2093 additions and 2140 deletions

11
Cargo.lock generated
View file

@ -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",

View file

@ -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]

View file

@ -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 }

View file

@ -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<Option<Self>, 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

View file

@ -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::<CommunityId>(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<Vec<CommunityId>, 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::<CommunityId>(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::<bool>(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::<bool>(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<Self, Error> {
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::<Self>(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::<Self>(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()
}

View file

@ -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::<bool>(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::<bool>(conn)
.await?
.then_some(())
.ok_or(LemmyErrorType::CommunityIsBlocked.into())
}
pub async fn for_person(
@ -42,7 +42,8 @@ impl CommunityBlock {
person_id: PersonId,
) -> Result<Vec<Community>, 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))

View file

@ -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::<bool>(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::<bool>(conn)
.await?
.then_some(())
.ok_or(LemmyErrorType::InstanceIsBlocked.into())
}
pub async fn for_person(
@ -42,7 +42,8 @@ impl InstanceBlock {
person_id: PersonId,
) -> Result<Vec<Instance>, 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))

View file

@ -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)

View file

@ -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<Vec<Person>, 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)

View file

@ -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::<bool>(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::<bool>(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))),

View file

@ -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<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(registration_application)
insert_into(registration_application::table)
.values(form)
.get_result::<Self>(conn)
.await
@ -32,7 +32,7 @@ impl Crud for RegistrationApplication {
form: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(registration_application.find(id_))
diesel::update(registration_application::table.find(id_))
.set(form)
.get_result::<Self>(conn)
.await
@ -45,9 +45,15 @@ impl RegistrationApplication {
local_user_id_: LocalUserId,
) -> Result<Self, Error> {
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()
}
}

View file

@ -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<aliases::Person1, person::id>,
AliasedField<aliases::Person1, person::name>,
AliasedField<aliases::Person1, person::display_name>,
AliasedField<aliases::Person1, person::avatar>,
AliasedField<aliases::Person1, person::banned>,
AliasedField<aliases::Person1, person::published>,
AliasedField<aliases::Person1, person::updated>,
AliasedField<aliases::Person1, person::actor_id>,
AliasedField<aliases::Person1, person::bio>,
AliasedField<aliases::Person1, person::local>,
AliasedField<aliases::Person1, person::private_key>,
AliasedField<aliases::Person1, person::public_key>,
AliasedField<aliases::Person1, person::last_refreshed_at>,
AliasedField<aliases::Person1, person::banner>,
AliasedField<aliases::Person1, person::deleted>,
AliasedField<aliases::Person1, person::inbox_url>,
AliasedField<aliases::Person1, person::matrix_user_id>,
AliasedField<aliases::Person1, person::bot_account>,
AliasedField<aliases::Person1, person::ban_expires>,
AliasedField<aliases::Person1, person::instance_id>,
);

View file

@ -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)))]

View file

@ -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<T: Expression + AsExpression<sql_types::Record<T::SqlType>>> AsRecord for T
/// Output of `IntoSql::into_sql` for a type that implements `AsRecord`
pub type AsRecordOutput<T> = dsl::AsExprOf<T, sql_types::Record<<T as Expression>::SqlType>>;
/// Output of `t.on((l0, l1).into_sql().eq((r0, r1)))`
type OnTupleEq<T, L0, L1, R0, R1> = dsl::On<T, dsl::Eq<AsRecordOutput<(L0, L1)>, (R0, R1)>>;
/// Creates an `ON` clause for a table where a person ID and another column are used as the
/// primary key. Use with the `QueryDsl::left_join` method.
///
/// This example modifies a query to make columns in `community_actions` available:
///
/// ```
/// community::table
/// .left_join(actions(
/// community_actions::table,
/// my_person_id,
/// community::id,
/// ))
/// ```
pub fn actions<T, P, C, K0, K1>(
actions_table: T,
person_id: Option<P>,
target_id: C,
) -> OnTupleEq<T, dsl::Nullable<K0>, K1, BindIfSome<dsl::AsExprOf<P, sql_types::Integer>>, C>
where
T: Table<PrimaryKey = (K0, K1)> + Copy,
K0: Expression,
P: AsExpression<sql_types::Integer>,
(dsl::Nullable<K0>, K1): AsRecord,
(BindIfSome<dsl::AsExprOf<P, sql_types::Integer>>, C):
AsExpression<<AsRecordOutput<(dsl::Nullable<K0>, K1)> as Expression>::SqlType>,
{
let (k0, k1) = actions_table.primary_key();
actions_table.on((k0.nullable(), k1).into_sql().eq((
BindIfSome(person_id.map(diesel::IntoSql::into_sql)),
target_id,
)))
}
/// Like `actions` but `actions_table` is an alias and person id is not nullable
#[allow(clippy::type_complexity)]
pub fn actions_alias<T, P, C, K0, K1>(
actions_table: Alias<T>,
person_id: P,
target_id: C,
) -> OnTupleEq<Alias<T>, AliasedField<T, K0>, AliasedField<T, K1>, P, C>
where
Alias<T>: QuerySource + Copy,
T: AliasSource<Target: Table<PrimaryKey = (K0, K1)>> + Default,
K0: Column<Table = T::Target>,
K1: Column<Table = T::Target>,
(AliasedField<T, K0>, AliasedField<T, K1>): AsRecord,
(P, C): AsExpression<
<AsRecordOutput<(AliasedField<T, K0>, AliasedField<T, K1>)> as Expression>::SqlType,
>,
{
let (k0, k1) = T::default().target().primary_key();
actions_table.on(
(actions_table.field(k0), actions_table.field(k1))
.into_sql()
.eq((person_id, target_id)),
)
}
/// `action_query(table_name::action_name)` is the same as
/// `table_name::table.filter(table_name::action_name.is_not_null())`.
pub fn action_query<C>(column: C) -> dsl::Filter<C::Table, dsl::IsNotNull<C>>
where
C: Column<Table: Default + FilterDsl<dsl::IsNotNull<C>>, SqlType: SingleValue>,
{
action_query_with_fn(column, |t| t)
}
/// `find_action(table_name::action_name, key)` is the same as
/// `table_name::table.find(key).filter(table_name::action_name.is_not_null())`.
pub fn find_action<C, K>(
column: C,
key: K,
) -> dsl::Filter<dsl::Find<C::Table, K>, dsl::IsNotNull<C>>
where
C:
Column<Table: Default + FindDsl<K, Output: FilterDsl<dsl::IsNotNull<C>>>, SqlType: SingleValue>,
{
action_query_with_fn(column, |t| t.find(key))
}
/// `action_query_with_fn(table_name::action_name, f)` is the same as
/// `f(table_name::table).filter(table_name::action_name.is_not_null())`.
fn action_query_with_fn<C, Q>(
column: C,
f: impl FnOnce(C::Table) -> Q,
) -> dsl::Filter<Q, dsl::IsNotNull<C>>
where
C: Column<Table: Default, SqlType: SingleValue>,
Q: FilterDsl<dsl::IsNotNull<C>>,
{
f(C::Table::default()).filter(column.is_not_null())
}
pub type ResultFuture<'a, T> = BoxFuture<'a, Result<T, DieselError>>;
pub 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<T>>
impl<'a, T, Args, F: Fn(DbConn<'a>, Args) -> ResultFuture<'a, Vec<T>>> 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<RF, LF> {
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 ReadFn<'a, RT, RA>, impl ListFn<'a, LT, LA>>
where
RFut: Future<Output = Result<RT, DieselError>> + Sized + Send + 'a,
LFut: Future<Output = Result<Vec<LT>, 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<RF, LF> Queries<RF, LF> {
pub async fn read<'a, T, Args>(
self,
pool: &'a mut DbPool<'_>,
args: Args,
) -> Result<T, DieselError>
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<Vec<T>, DieselError>
where
LF: ListFn<'a, T, Args>,
{
let conn = get_conn(pool).await?;
(self.list_fn)(conn, args).await
}
}
pub fn paginate<Q, C>(
query: Q,
page_after: Option<C>,

View file

@ -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 }

View file

@ -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<i64, Error> {
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<i64, Error> {
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::<i64>(conn)
.await
query.first::<i64>(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::<diesel::sql_types::Json>(
@ -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(),

View file

@ -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<PersonId>, hide_modlog_names: Option<bool>) -> _ {
// 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::<diesel::sql_types::Bool>();
// 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<Vec<ModlogCombinedView>> {
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::<diesel::sql_types::Bool>();
// 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 {

View file

@ -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<PersonId>) -> _ {
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() {

View file

@ -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<Vec<PersonContentCombinedView>> {
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() {

View file

@ -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::<i64>(conn)
.await
query.first::<i64>(conn).await
}
}
@ -164,102 +268,9 @@ impl ReportCombinedQuery {
user: &LocalUserView,
) -> LemmyResult<Vec<ReportCombinedView>> {
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(),

View file

@ -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<PersonId>) -> _ {
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(),

View file

@ -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<PersonId>) -> _ {
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<PersonId>| {
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<Self, Error> {
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::<Self>(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<ListingType>,
pub sort: Option<CommentSortType>,
pub community_id: Option<CommunityId>,
pub post_id: Option<PostId>,
pub parent_path: Option<Ltree>,
pub creator_id: Option<PersonId>,
pub local_user: Option<&'a LocalUser>,
pub search_term: Option<String>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub page: Option<i64>,
pub limit: Option<i64>,
pub max_depth: Option<i32>,
}
impl CommentQuery<'_> {
pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, 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::<CommentView>(&mut conn)
.await
};
.load::<CommentView>(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<Self, Error> {
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<ListingType>,
pub sort: Option<CommentSortType>,
pub community_id: Option<CommunityId>,
pub post_id: Option<PostId>,
pub parent_path: Option<Ltree>,
pub creator_id: Option<PersonId>,
pub local_user: Option<&'a LocalUser>,
pub search_term: Option<String>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub page: Option<i64>,
pub limit: Option<i64>,
pub max_depth: Option<i32>,
}
impl CommentQuery<'_> {
pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, 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();

View file

@ -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<Vec<DbUrl>, 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::<DbUrl>(conn)
.await?;
Ok(res)
}
pub async fn count_community_followers(
pool: &mut DbPool<'_>,
community_id: CommunityId,
) -> Result<i64, Error> {
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::<i64>(conn)
@ -69,13 +92,11 @@ impl CommunityFollowerView {
pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Vec<Self>, 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::<CommunityFollowerView>(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<i64, Error> {
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)),

View file

@ -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::<bool>(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::<bool>(conn)
.await?
@ -47,13 +63,11 @@ impl CommunityModeratorView {
community_id: CommunityId,
) -> Result<Vec<Self>, 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::<CommunityModeratorView>(conn)
.load::<Self>(conn)
.await
}
@ -63,11 +77,9 @@ impl CommunityModeratorView {
local_user: Option<&LocalUser>,
) -> Result<Vec<Self>, 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::<CommunityModeratorView>(conn).await
query.load::<Self>(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<Vec<Self>, 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::<CommunityModeratorView>(conn)
.load::<Self>(conn)
.await
}
}

View file

@ -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::<bool>(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::<bool>(conn)
.await?
.then_some(())
.ok_or(LemmyErrorType::PersonIsBannedFromCommunity.into())
}
}

View file

@ -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::<CommunityView>(&mut conn)
.await
};
Queries::new(read, list)
}
impl CommunityView {
#[diesel::dsl::auto_type(no_type_alias)]
fn joins(person_id: Option<PersonId>) -> _ {
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<Self, Error> {
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<PostSortType> 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<ListingType>,
pub sort: Option<CommunitySortType>,
pub local_user: Option<&'a LocalUser>,
pub search_term: Option<String>,
pub title_only: Option<bool>,
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<Vec<CommunityView>, 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::<CommunityView>(conn)
.await
}
}

View file

@ -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 ReadFn<'a, LocalUserView, ReadBy<'a>>, 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::<LocalUserView>(&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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Vec<Self>, 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::<Self>(conn)
.await
}
pub async fn create_test_user(

View file

@ -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 ReadFn<'a, PersonView, (PersonId, bool)>, 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::<PersonView>(&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<Self, Error> {
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<Vec<Self>, 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::<Self>(conn)
.await
}
pub async fn banned(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
queries().list(pool, ListMode::Banned).await
}
}
#[derive(Default)]
pub struct PersonQuery {
pub sort: Option<PostSortType>,
pub search_term: Option<String>,
pub listing_type: Option<ListingType>,
pub page: Option<i64>,
pub limit: Option<i64>,
}
impl PersonQuery {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonView>, 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::<Self>(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
}
}

View file

@ -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<PersonId>) -> _ {
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<Self, Error> {
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<PersonId>| {
// 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::<diesel::sql_types::Json>(
@ -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<PaginationCursorData, Error> {
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<ListingType>,
pub sort: Option<PostSortType>,
pub creator_id: Option<PersonId>,
pub community_id: Option<CommunityId>,
// 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<String>,
pub url_only: Option<bool>,
pub read_only: Option<bool>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub title_only: Option<bool>,
pub page: Option<i64>,
pub limit: Option<i64>,
pub page_after: Option<PaginationCursorData>,
pub page_before_or_equal: Option<PostAggregates>,
pub page_back: Option<bool>,
pub show_hidden: Option<bool>,
pub show_read: Option<bool>,
pub show_nsfw: Option<bool>,
pub hide_media: Option<bool>,
pub no_comments_only: Option<bool>,
}
impl<'a> PostQuery<'a> {
#[allow(clippy::expect_used)]
async fn prefetch_upper_bound_for_page_before(
&self,
site: &Site,
pool: &mut DbPool<'_>,
) -> Result<Option<PostQuery<'a>>, 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::<CommunityId>(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<Vec<PostView>, 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::<diesel::sql_types::Json>(
"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::<PostView>(&mut conn)
.load::<PostView>(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<Self, Error> {
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<PaginationCursorData, Error> {
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<ListingType>,
pub sort: Option<PostSortType>,
pub creator_id: Option<PersonId>,
pub community_id: Option<CommunityId>,
// 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<String>,
pub url_only: Option<bool>,
pub read_only: Option<bool>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub title_only: Option<bool>,
pub page: Option<i64>,
pub limit: Option<i64>,
pub page_after: Option<PaginationCursorData>,
pub page_before_or_equal: Option<PostAggregates>,
pub page_back: Option<bool>,
pub show_hidden: Option<bool>,
pub show_read: Option<bool>,
pub show_nsfw: Option<bool>,
pub hide_media: Option<bool>,
pub no_comments_only: Option<bool>,
}
impl<'a> PostQuery<'a> {
#[allow(clippy::expect_used)]
async fn prefetch_upper_bound_for_page_before(
&self,
site: &Site,
pool: &mut DbPool<'_>,
) -> Result<Option<PostQuery<'a>>, 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::<CommunityId>(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<Vec<PostView>, 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
}
}
}

View file

@ -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<Self, Error> {
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
}

View file

@ -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::<RegistrationApplicationView>(&mut conn).await
};
Queries::new(read, list)
}
impl RegistrationApplicationView {
pub async fn read(pool: &mut DbPool<'_>, id: RegistrationApplicationId) -> Result<Self, Error> {
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<Self, Error> {
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<i64, Error> {
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::<i64>(conn)
.await
query.first::<i64>(conn).await
}
}
@ -130,7 +80,32 @@ impl RegistrationApplicationQuery {
self,
pool: &mut DbPool<'_>,
) -> Result<Vec<RegistrationApplicationView>, 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::<RegistrationApplicationView>(conn)
.await
}
}

View file

@ -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<Self, Error> {
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,

View file

@ -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<Self, Error> {
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,

View file

@ -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 = (
<custom_emoji::table as diesel::Table>::AllColumns,
Nullable<<custom_emoji_keyword::table as diesel::Table>::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<CustomEmojiKeyword>);
// 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<Self, Error> {
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::<CustomEmojiTuple>(conn)
.await?;
if let Some(emoji) = CustomEmojiView::from_tuple_to_vec(emojis)
@ -44,12 +64,7 @@ impl CustomEmojiView {
) -> Result<Vec<Self>, 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::<CustomEmojiTuple>(conn)
.await?;

View file

@ -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<LocalUserId>,
page: Option<i64>,
limit: Option<i64>,
ignore_page_limits: bool,
) -> Result<Vec<Self>, 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::<LocalImageView>(conn).await
}
pub async fn get_all_paged_by_local_user_id(
@ -41,14 +21,28 @@ impl LocalImageView {
page: Option<i64>,
limit: Option<i64>,
) -> Result<Vec<Self>, 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::<Self>(conn)
.await
}
pub async fn get_all_by_local_user_id(
pool: &mut DbPool<'_>,
user_id: LocalUserId,
) -> Result<Vec<Self>, 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::<Self>(conn)
.await
}
pub async fn get_all(
@ -56,6 +50,13 @@ impl LocalImageView {
page: Option<i64>,
limit: Option<i64>,
) -> Result<Vec<Self>, 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::<Self>(conn)
.await
}
}

View file

@ -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()?

View file

@ -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,

View file

@ -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<community_actions::follow_state>,
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<i16>,
}
@ -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<Person1AliasAllColumnsTuple>,
select_expression = person1.fields(person::all_columns).nullable()
)
)]
pub admin: Option<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 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<community_actions::follow_state>,
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<diesel::sql_types::Bool, Nullable<local_user::admin>, 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<AdminAllowInstance>,
#[cfg_attr(feature = "full", diesel(embed))]
pub admin_block_instance: Option<AdminBlockInstance>,
#[cfg_attr(feature = "full", diesel(embed))]
pub admin_purge_comment: Option<AdminPurgeComment>,
#[cfg_attr(feature = "full", diesel(embed))]
pub admin_purge_community: Option<AdminPurgeCommunity>,
#[cfg_attr(feature = "full", diesel(embed))]
pub admin_purge_person: Option<AdminPurgePerson>,
#[cfg_attr(feature = "full", diesel(embed))]
pub admin_purge_post: Option<AdminPurgePost>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_add: Option<ModAdd>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_add_community: Option<ModAddCommunity>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_ban: Option<ModBan>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_ban_from_community: Option<ModBanFromCommunity>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_feature_post: Option<ModFeaturePost>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_hide_community: Option<ModHideCommunity>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_lock_post: Option<ModLockPost>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_remove_comment: Option<ModRemoveComment>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_remove_community: Option<ModRemoveCommunity>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_remove_post: Option<ModRemovePost>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_transfer_community: Option<ModTransferCommunity>,
// Specific fields
// Shared
#[cfg_attr(feature = "full", diesel(embed))]
pub moderator: Option<Person>,
#[cfg_attr(feature = "full",
diesel(
select_expression_type = Nullable<Person1AliasAllColumnsTuple>,
select_expression = person1.fields(person::all_columns).nullable()
)
)]
pub other_person: Option<Person>,
#[cfg_attr(feature = "full", diesel(embed))]
pub instance: Option<Instance>,
#[cfg_attr(feature = "full", diesel(embed))]
pub community: Option<Community>,
#[cfg_attr(feature = "full", diesel(embed))]
pub post: Option<Post>,
#[cfg_attr(feature = "full", diesel(embed))]
pub comment: Option<Comment>,
}

View file

@ -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<LemmyContext>) -> 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<LemmyContext>) -> 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?;

View file

@ -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<T> = Result<T, LemmyError>;
pub struct LemmyError {