Use same table join code for both read and list functions (#3663)

* Try stuff

* Revert "Try stuff"

This reverts commit 3da5f83a8b.

* Revert "Revert "Try stuff""

This reverts commit 178bd43cac.

* Revert "Revert "Revert "Try stuff"""

This reverts commit b9f9a2316e.

* Revert "Revert "Revert "Revert "Try stuff""""

This reverts commit ccd498dd72.

* Try more stuff

* Add queries function

* Simplify queries function

* Move aliases to db_schema

* Revert "Move aliases to db_schema"

This reverts commit 69afed05c1.

* Add ReadFuture and ListFuture

* Refactor queries function and add Queries struct

* Box futures in Queries::new

* Use from_tuple

* Add comment_view::queries and improve comment_report_view::queries

* Add local_user_view::queries

* Add post_report_view::queries

* Ad post_view::queries

* Add private_message_report_view::queries

* private_message_view, registration_application_view

* Use 'a in BoxedQuery

* comment_reply_view, community_view

* Change aliases to inline module

* person_mention_view

* person_view

* Use separate community_person_ban joins instead of including boolean literal in join-on clause

* Fix comment_view

* rerun ci
This commit is contained in:
dullbananas 2023-07-28 01:36:50 -07:00 committed by GitHub
parent e315092ee3
commit 9a5a13c734
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1233 additions and 1521 deletions

View file

@ -28,6 +28,11 @@ pub mod newtypes;
#[rustfmt::skip] #[rustfmt::skip]
#[allow(clippy::wildcard_imports)] #[allow(clippy::wildcard_imports)]
pub mod schema; pub mod schema;
#[cfg(feature = "full")]
pub mod aliases {
use crate::schema::person;
diesel::alias!(person as person1: Person1, person as person2: Person2);
}
pub mod source; pub mod source;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod traits; pub mod traits;

View file

@ -2,6 +2,7 @@ use crate::{
diesel::Connection, diesel::Connection,
diesel_migrations::MigrationHarness, diesel_migrations::MigrationHarness,
newtypes::DbUrl, newtypes::DbUrl,
traits::JoinView,
CommentSortType, CommentSortType,
PersonSortType, PersonSortType,
SortType, SortType,
@ -26,7 +27,7 @@ use diesel_async::{
}, },
}; };
use diesel_migrations::EmbeddedMigrations; use diesel_migrations::EmbeddedMigrations;
use futures_util::{future::BoxFuture, FutureExt}; use futures_util::{future::BoxFuture, Future, FutureExt};
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType}, error::{LemmyError, LemmyErrorExt, LemmyErrorType},
settings::structs::Settings, settings::structs::Settings,
@ -420,6 +421,94 @@ where
} }
} }
pub type ResultFuture<'a, T> = BoxFuture<'a, Result<T, DieselError>>;
pub trait ReadFn<'a, T: JoinView, Args>:
Fn(DbConn<'a>, Args) -> ResultFuture<'a, <T as JoinView>::JoinTuple>
{
}
impl<
'a,
T: JoinView,
Args,
F: Fn(DbConn<'a>, Args) -> ResultFuture<'a, <T as JoinView>::JoinTuple>,
> ReadFn<'a, T, Args> for F
{
}
pub trait ListFn<'a, T: JoinView, Args>:
Fn(DbConn<'a>, Args) -> ResultFuture<'a, Vec<<T as JoinView>::JoinTuple>>
{
}
impl<
'a,
T: JoinView,
Args,
F: Fn(DbConn<'a>, Args) -> ResultFuture<'a, Vec<<T as JoinView>::JoinTuple>>,
> 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 as JoinView>::JoinTuple, DieselError>> + Sized + Send + 'a,
LFut:
Future<Output = Result<Vec<<LT as JoinView>::JoinTuple>, DieselError>> + Sized + Send + 'a,
RT: JoinView,
LT: JoinView,
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
T: JoinView,
RF: ReadFn<'a, T, Args>,
{
let conn = get_conn(pool).await?;
let res = (self.read_fn)(conn, args).await?;
Ok(T::from_tuple(res))
}
pub async fn list<'a, T, Args>(
self,
pool: &'a mut DbPool<'_>,
args: Args,
) -> Result<Vec<T>, DieselError>
where
T: JoinView,
LF: ListFn<'a, T, Args>,
{
let conn = get_conn(pool).await?;
let res = (self.list_fn)(conn, args).await?;
Ok(res.into_iter().map(T::from_tuple).collect())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)] #![allow(clippy::unwrap_used)]

View file

@ -1,6 +1,7 @@
use crate::structs::CommentReportView; use crate::structs::CommentReportView;
use diesel::{ use diesel::{
dsl::now, dsl::now,
pg::Pg,
result::Error, result::Error,
BoolExpressionMethods, BoolExpressionMethods,
ExpressionMethods, ExpressionMethods,
@ -11,6 +12,7 @@ use diesel::{
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::CommentAggregates, aggregates::structs::CommentAggregates,
aliases,
newtypes::{CommentReportId, CommunityId, PersonId}, newtypes::{CommentReportId, CommunityId, PersonId},
schema::{ schema::{
comment, comment,
@ -31,9 +33,119 @@ use lemmy_db_schema::{
post::Post, post::Post,
}, },
traits::JoinView, traits::JoinView,
utils::{get_conn, limit_and_offset, DbPool}, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
}; };
fn queries<'a>() -> Queries<
impl ReadFn<'a, CommentReportView, (CommentReportId, PersonId)>,
impl ListFn<'a, CommentReportView, (CommentReportQuery, &'a Person)>,
> {
let all_joins = |query: comment_report::BoxedQuery<'a, Pg>, my_person_id: PersonId| {
query
.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(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(my_person_id)),
),
)
.left_join(
aliases::person2
.on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())),
)
};
let selection = (
comment_report::all_columns,
comment::all_columns,
post::all_columns,
community::all_columns,
person::all_columns,
aliases::person1.fields(person::all_columns),
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
comment_like::score.nullable(),
aliases::person2.fields(person::all_columns).nullable(),
);
let read = move |mut conn: DbConn<'a>, (report_id, my_person_id): (CommentReportId, PersonId)| async move {
all_joins(
comment_report::table.find(report_id).into_boxed(),
my_person_id,
)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
)
.select(selection)
.first::<<CommentReportView as JoinView>::JoinTuple>(&mut conn)
.await
};
let list = move |mut conn: DbConn<'a>, (options, my_person): (CommentReportQuery, &'a Person)| async move {
let mut query = all_joins(comment_report::table.into_boxed(), my_person.id)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id))
.and(
community_person_ban::expires
.is_null()
.or(community_person_ban::expires.gt(now)),
),
),
)
.select(selection);
if let Some(community_id) = options.community_id {
query = query.filter(post::community_id.eq(community_id));
}
if options.unresolved_only.unwrap_or(false) {
query = query.filter(comment_report::resolved.eq(false));
}
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
query = query
.order_by(comment_report::published.desc())
.limit(limit)
.offset(offset);
// If its not an admin, get only the ones you mod
if !my_person.admin {
query
.inner_join(
community_moderator::table.on(
community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(my_person.id)),
),
)
.load::<<CommentReportView as JoinView>::JoinTuple>(&mut conn)
.await
} else {
query
.load::<<CommentReportView as JoinView>::JoinTuple>(&mut conn)
.await
}
};
Queries::new(read, list)
}
impl CommentReportView { impl CommentReportView {
/// returns the CommentReportView for the provided report_id /// returns the CommentReportView for the provided report_id
/// ///
@ -43,54 +155,7 @@ impl CommentReportView {
report_id: CommentReportId, report_id: CommentReportId,
my_person_id: PersonId, my_person_id: PersonId,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; queries().read(pool, (report_id, my_person_id)).await
let (person_alias_1, person_alias_2) = diesel::alias!(person as person1, person as person2);
let res = 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(person_alias_1.on(comment::creator_id.eq(person_alias_1.field(person::id))))
.inner_join(
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
)
.left_join(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(my_person_id)),
),
)
.left_join(
person_alias_2
.on(comment_report::resolver_id.eq(person_alias_2.field(person::id).nullable())),
)
.select((
comment_report::all_columns,
comment::all_columns,
post::all_columns,
community::all_columns,
person::all_columns,
person_alias_1.fields(person::all_columns),
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
comment_like::score.nullable(),
person_alias_2.fields(person::all_columns).nullable(),
))
.first::<<CommentReportView as JoinView>::JoinTuple>(conn)
.await?;
Ok(Self::from_tuple(res))
} }
/// Returns the current unresolved post report count for the communities you mod /// Returns the current unresolved post report count for the communities you mod
@ -150,90 +215,7 @@ impl CommentReportQuery {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
my_person: &Person, my_person: &Person,
) -> Result<Vec<CommentReportView>, Error> { ) -> Result<Vec<CommentReportView>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, (self, my_person)).await
let (person_alias_1, person_alias_2) = diesel::alias!(person as person1, person as person2);
let mut query = comment_report::table
.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(person_alias_1.on(comment::creator_id.eq(person_alias_1.field(person::id))))
.inner_join(
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id))
.and(
community_person_ban::expires
.is_null()
.or(community_person_ban::expires.gt(now)),
),
),
)
.left_join(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(my_person.id)),
),
)
.left_join(
person_alias_2
.on(comment_report::resolver_id.eq(person_alias_2.field(person::id).nullable())),
)
.select((
comment_report::all_columns,
comment::all_columns,
post::all_columns,
community::all_columns,
person::all_columns,
person_alias_1.fields(person::all_columns),
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
comment_like::score.nullable(),
person_alias_2.fields(person::all_columns).nullable(),
))
.into_boxed();
if let Some(community_id) = self.community_id {
query = query.filter(post::community_id.eq(community_id));
}
if self.unresolved_only.unwrap_or(false) {
query = query.filter(comment_report::resolved.eq(false));
}
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
query = query
.order_by(comment_report::published.desc())
.limit(limit)
.offset(offset);
// If its not an admin, get only the ones you mod
let res = if !my_person.admin {
query
.inner_join(
community_moderator::table.on(
community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(my_person.id)),
),
)
.load::<<CommentReportView as JoinView>::JoinTuple>(conn)
.await?
} else {
query
.load::<<CommentReportView as JoinView>::JoinTuple>(conn)
.await?
};
Ok(res.into_iter().map(CommentReportView::from_tuple).collect())
} }
} }

View file

@ -1,5 +1,6 @@
use crate::structs::{CommentView, LocalUserView}; use crate::structs::{CommentView, LocalUserView};
use diesel::{ use diesel::{
pg::Pg,
result::Error, result::Error,
BoolExpressionMethods, BoolExpressionMethods,
ExpressionMethods, ExpressionMethods,
@ -35,7 +36,7 @@ use lemmy_db_schema::{
post::Post, post::Post,
}, },
traits::JoinView, traits::JoinView,
utils::{fuzzy_search, get_conn, limit_and_offset, DbPool}, utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
CommentSortType, CommentSortType,
ListingType, ListingType,
}; };
@ -53,30 +54,14 @@ type CommentViewTuple = (
Option<i16>, Option<i16>,
); );
impl CommentView { fn queries<'a>() -> Queries<
pub async fn read( impl ReadFn<'a, CommentView, (CommentId, Option<PersonId>)>,
pool: &mut DbPool<'_>, impl ListFn<'a, CommentView, CommentQuery<'a>>,
comment_id: CommentId, > {
my_person_id: Option<PersonId>, let all_joins = |query: comment::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| {
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
// The left join below will return None in this case // The left join below will return None in this case
let person_id_join = my_person_id.unwrap_or(PersonId(-1)); let person_id_join = my_person_id.unwrap_or(PersonId(-1));
query
let (
comment,
creator,
post,
community,
counts,
creator_banned_from_community,
follower,
saved,
creator_blocked,
comment_like,
) = comment::table
.find(comment_id)
.inner_join(person::table) .inner_join(person::table)
.inner_join(post::table) .inner_join(post::table)
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
@ -116,41 +101,201 @@ impl CommentView {
.and(comment_like::person_id.eq(person_id_join)), .and(comment_like::person_id.eq(person_id_join)),
), ),
) )
.select(( };
comment::all_columns,
person::all_columns,
post::all_columns,
community::all_columns,
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
community_follower::all_columns.nullable(),
comment_saved::all_columns.nullable(),
person_block::all_columns.nullable(),
comment_like::score.nullable(),
))
.first::<CommentViewTuple>(conn)
.await?;
// If a person is given, then my_vote, if None, should be 0, not null let selection = (
// Necessary to differentiate between other person's votes comment::all_columns,
let my_vote = if my_person_id.is_some() && comment_like.is_none() { person::all_columns,
Some(0) post::all_columns,
} else { community::all_columns,
comment_like comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
community_follower::all_columns.nullable(),
comment_saved::all_columns.nullable(),
person_block::all_columns.nullable(),
comment_like::score.nullable(),
);
let read = move |mut conn: DbConn<'a>,
(comment_id, my_person_id): (CommentId, Option<PersonId>)| async move {
all_joins(comment::table.find(comment_id).into_boxed(), my_person_id)
.select(selection)
.first::<CommentViewTuple>(&mut conn)
.await
};
let list = move |mut conn: DbConn<'a>, options: CommentQuery<'a>| async move {
let person_id = options.local_user.map(|l| l.person.id);
let local_user_id = options.local_user.map(|l| l.local_user.id);
// The left join below will return None in this case
let person_id_join = person_id.unwrap_or(PersonId(-1));
let local_user_id_join = local_user_id.unwrap_or(LocalUserId(-1));
let mut query = all_joins(comment::table.into_boxed(), person_id)
.left_join(
community_block::table.on(
community::id
.eq(community_block::community_id)
.and(community_block::person_id.eq(person_id_join)),
),
)
.left_join(
local_user_language::table.on(
comment::language_id
.eq(local_user_language::language_id)
.and(local_user_language::local_user_id.eq(local_user_id_join)),
),
)
.select(selection);
if let Some(creator_id) = options.creator_id {
query = query.filter(comment::creator_id.eq(creator_id));
}; };
Ok(CommentView { if let Some(post_id) = options.post_id {
comment, query = query.filter(comment::post_id.eq(post_id));
post, };
creator,
community, if let Some(parent_path) = options.parent_path.as_ref() {
counts, query = query.filter(comment::path.contained_by(parent_path));
creator_banned_from_community: creator_banned_from_community.is_some(), };
subscribed: CommunityFollower::to_subscribed_type(&follower),
saved: saved.is_some(), if let Some(search_term) = options.search_term {
creator_blocked: creator_blocked.is_some(), query = query.filter(comment::content.ilike(fuzzy_search(&search_term)));
my_vote, };
})
if let Some(community_id) = options.community_id {
query = query.filter(post::community_id.eq(community_id));
}
if let Some(listing_type) = options.listing_type {
match listing_type {
ListingType::Subscribed => {
query = query.filter(community_follower::person_id.is_not_null())
} // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
ListingType::Local => {
query = query.filter(community::local.eq(true)).filter(
community::hidden
.eq(false)
.or(community_follower::person_id.eq(person_id_join)),
)
}
ListingType::All => {
query = query.filter(
community::hidden
.eq(false)
.or(community_follower::person_id.eq(person_id_join)),
)
}
}
}
if options.saved_only.unwrap_or(false) {
query = query.filter(comment_saved::comment_id.is_not_null());
}
let is_profile_view = options.is_profile_view.unwrap_or(false);
let is_creator = options.creator_id == options.local_user.map(|l| l.person.id);
// only show deleted comments to creator
if !is_creator {
query = query.filter(comment::deleted.eq(false));
}
let is_admin = options.local_user.map(|l| l.person.admin).unwrap_or(false);
// only show removed comments to admin when viewing user profile
if !(is_profile_view && is_admin) {
query = query.filter(comment::removed.eq(false));
}
if !options
.local_user
.map(|l| l.local_user.show_bot_accounts)
.unwrap_or(true)
{
query = query.filter(person::bot_account.eq(false));
};
if options.local_user.is_some() {
// Filter out the rows with missing languages
query = query.filter(local_user_language::language_id.is_not_null());
// Don't show blocked communities or persons
if options.post_id.is_none() {
query = query.filter(community_block::person_id.is_null());
}
query = query.filter(person_block::person_id.is_null());
}
// A Max depth given means its a tree fetch
let (limit, offset) = if let Some(max_depth) = options.max_depth {
let depth_limit = if let Some(parent_path) = options.parent_path.as_ref() {
parent_path.0.split('.').count() as i32 + max_depth
// Add one because of root "0"
} else {
max_depth + 1
};
query = query.filter(nlevel(comment::path).le(depth_limit));
// only order if filtering by a post id. DOS potential otherwise and max_depth + !post_id isn't used anyways (afaik)
if options.post_id.is_some() {
// Always order by the parent path first
query = query.order_by(subpath(comment::path, 0, -1));
}
// TODO limit question. Limiting does not work for comment threads ATM, only max_depth
// For now, don't do any limiting for tree fetches
// https://stackoverflow.com/questions/72983614/postgres-ltree-how-to-limit-the-max-number-of-children-at-any-given-level
// Don't use the regular error-checking one, many more comments must ofter be fetched.
// This does not work for comment trees, and the limit should be manually set to a high number
//
// If a max depth is given, then you know its a tree fetch, and limits should be ignored
// TODO a kludge to prevent attacks. Limit comments to 300 for now.
// (i64::MAX, 0)
(300, 0)
} else {
// limit_and_offset_unlimited(options.page, options.limit)
limit_and_offset(options.page, options.limit)?
};
query = match options.sort.unwrap_or(CommentSortType::Hot) {
CommentSortType::Hot => query
.then_order_by(comment_aggregates::hot_rank.desc())
.then_order_by(comment_aggregates::score.desc()),
CommentSortType::Controversial => {
query.then_order_by(comment_aggregates::controversy_rank.desc())
}
CommentSortType::New => query.then_order_by(comment::published.desc()),
CommentSortType::Old => query.then_order_by(comment::published.asc()),
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
};
// Note: deleted and removed comments are done on the front side
query
.limit(limit)
.offset(offset)
.load::<CommentViewTuple>(&mut conn)
.await
};
Queries::new(read, list)
}
impl CommentView {
pub async fn read(
pool: &mut DbPool<'_>,
comment_id: CommentId,
my_person_id: Option<PersonId>,
) -> Result<Self, Error> {
// 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_person_id)).await?;
if my_person_id.is_some() && res.my_vote.is_none() {
res.my_vote = Some(0);
}
Ok(res)
} }
} }
@ -174,214 +319,7 @@ pub struct CommentQuery<'a> {
impl<'a> CommentQuery<'a> { impl<'a> CommentQuery<'a> {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, Error> { pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, self).await
// The left join below will return None in this case
let person_id_join = self.local_user.map(|l| l.person.id).unwrap_or(PersonId(-1));
let local_user_id_join = self
.local_user
.map(|l| l.local_user.id)
.unwrap_or(LocalUserId(-1));
let mut query = comment::table
.inner_join(person::table)
.inner_join(post::table)
.inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(comment_aggregates::table)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
)
.left_join(
community_follower::table.on(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id_join)),
),
)
.left_join(
comment_saved::table.on(
comment::id
.eq(comment_saved::comment_id)
.and(comment_saved::person_id.eq(person_id_join)),
),
)
.left_join(
person_block::table.on(
comment::creator_id
.eq(person_block::target_id)
.and(person_block::person_id.eq(person_id_join)),
),
)
.left_join(
community_block::table.on(
community::id
.eq(community_block::community_id)
.and(community_block::person_id.eq(person_id_join)),
),
)
.left_join(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(person_id_join)),
),
)
.left_join(
local_user_language::table.on(
comment::language_id
.eq(local_user_language::language_id)
.and(local_user_language::local_user_id.eq(local_user_id_join)),
),
)
.select((
comment::all_columns,
person::all_columns,
post::all_columns,
community::all_columns,
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
community_follower::all_columns.nullable(),
comment_saved::all_columns.nullable(),
person_block::all_columns.nullable(),
comment_like::score.nullable(),
))
.into_boxed();
if let Some(creator_id) = self.creator_id {
query = query.filter(comment::creator_id.eq(creator_id));
};
if let Some(post_id) = self.post_id {
query = query.filter(comment::post_id.eq(post_id));
};
if let Some(parent_path) = self.parent_path.as_ref() {
query = query.filter(comment::path.contained_by(parent_path));
};
if let Some(search_term) = self.search_term {
query = query.filter(comment::content.ilike(fuzzy_search(&search_term)));
};
if let Some(community_id) = self.community_id {
query = query.filter(post::community_id.eq(community_id));
}
if let Some(listing_type) = self.listing_type {
match listing_type {
ListingType::Subscribed => {
query = query.filter(community_follower::person_id.is_not_null())
} // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
ListingType::Local => {
query = query.filter(community::local.eq(true)).filter(
community::hidden
.eq(false)
.or(community_follower::person_id.eq(person_id_join)),
)
}
ListingType::All => {
query = query.filter(
community::hidden
.eq(false)
.or(community_follower::person_id.eq(person_id_join)),
)
}
}
}
if self.saved_only.unwrap_or(false) {
query = query.filter(comment_saved::comment_id.is_not_null());
}
let is_profile_view = self.is_profile_view.unwrap_or(false);
let is_creator = self.creator_id == self.local_user.map(|l| l.person.id);
// only show deleted comments to creator
if !is_creator {
query = query.filter(comment::deleted.eq(false));
}
let is_admin = self.local_user.map(|l| l.person.admin).unwrap_or(false);
// only show removed comments to admin when viewing user profile
if !(is_profile_view && is_admin) {
query = query.filter(comment::removed.eq(false));
}
if !self
.local_user
.map(|l| l.local_user.show_bot_accounts)
.unwrap_or(true)
{
query = query.filter(person::bot_account.eq(false));
};
if self.local_user.is_some() {
// Filter out the rows with missing languages
query = query.filter(local_user_language::language_id.is_not_null());
// Don't show blocked communities or persons
if self.post_id.is_none() {
query = query.filter(community_block::person_id.is_null());
}
query = query.filter(person_block::person_id.is_null());
}
// A Max depth given means its a tree fetch
let (limit, offset) = if let Some(max_depth) = self.max_depth {
let depth_limit = if let Some(parent_path) = self.parent_path.as_ref() {
parent_path.0.split('.').count() as i32 + max_depth
// Add one because of root "0"
} else {
max_depth + 1
};
query = query.filter(nlevel(comment::path).le(depth_limit));
// only order if filtering by a post id. DOS potential otherwise and max_depth + !post_id isn't used anyways (afaik)
if self.post_id.is_some() {
// Always order by the parent path first
query = query.order_by(subpath(comment::path, 0, -1));
}
// TODO limit question. Limiting does not work for comment threads ATM, only max_depth
// For now, don't do any limiting for tree fetches
// https://stackoverflow.com/questions/72983614/postgres-ltree-how-to-limit-the-max-number-of-children-at-any-given-level
// Don't use the regular error-checking one, many more comments must ofter be fetched.
// This does not work for comment trees, and the limit should be manually set to a high number
//
// If a max depth is given, then you know its a tree fetch, and limits should be ignored
// TODO a kludge to prevent attacks. Limit comments to 300 for now.
// (i64::MAX, 0)
(300, 0)
} else {
// limit_and_offset_unlimited(self.page, self.limit)
limit_and_offset(self.page, self.limit)?
};
query = match self.sort.unwrap_or(CommentSortType::Hot) {
CommentSortType::Hot => query
.then_order_by(comment_aggregates::hot_rank.desc())
.then_order_by(comment_aggregates::score.desc()),
CommentSortType::Controversial => {
query.then_order_by(comment_aggregates::controversy_rank.desc())
}
CommentSortType::New => query.then_order_by(comment::published.desc()),
CommentSortType::Old => query.then_order_by(comment::published.asc()),
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
};
// Note: deleted and removed comments are done on the front side
let res = query
.limit(limit)
.offset(offset)
.load::<CommentViewTuple>(conn)
.await?;
Ok(res.into_iter().map(CommentView::from_tuple).collect())
} }
} }

View file

@ -7,136 +7,102 @@ use lemmy_db_schema::{
schema::{local_user, person, person_aggregates}, schema::{local_user, person, person_aggregates},
source::{local_user::LocalUser, person::Person}, source::{local_user::LocalUser, person::Person},
traits::JoinView, traits::JoinView,
utils::{functions::lower, get_conn, DbPool}, utils::{functions::lower, DbConn, DbPool, ListFn, Queries, ReadFn},
}; };
type LocalUserViewTuple = (LocalUser, Person, PersonAggregates); type LocalUserViewTuple = (LocalUser, Person, PersonAggregates);
enum ReadBy<'a> {
Id(LocalUserId),
Person(PersonId),
Name(&'a str),
NameOrEmail(&'a str),
Email(&'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,
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(local_user::email.eq(from_email)),
_ => 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))
.or(local_user::email.eq(name_or_email)),
),
_ => query,
};
query
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select(selection)
.first::<LocalUserViewTuple>(&mut conn)
.await
};
let list = move |mut conn: DbConn<'a>, mode: ListMode| async move {
match mode {
ListMode::AdminsWithEmails => {
local_user::table
.filter(local_user::email.is_not_null())
.filter(person::admin.eq(true))
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select(selection)
.load::<LocalUserViewTuple>(&mut conn)
.await
}
}
};
Queries::new(read, list)
}
impl LocalUserView { impl LocalUserView {
pub async fn read(pool: &mut DbPool<'_>, local_user_id: LocalUserId) -> Result<Self, Error> { pub async fn read(pool: &mut DbPool<'_>, local_user_id: LocalUserId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; queries().read(pool, ReadBy::Id(local_user_id)).await
let (local_user, person, counts) = local_user::table
.find(local_user_id)
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)
.await?;
Ok(Self {
local_user,
person,
counts,
})
} }
pub async fn read_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Self, Error> { pub async fn read_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; queries().read(pool, ReadBy::Person(person_id)).await
let (local_user, person, counts) = local_user::table
.filter(person::id.eq(person_id))
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)
.await?;
Ok(Self {
local_user,
person,
counts,
})
} }
pub async fn read_from_name(pool: &mut DbPool<'_>, name: &str) -> Result<Self, Error> { pub async fn read_from_name(pool: &mut DbPool<'_>, name: &str) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; queries().read(pool, ReadBy::Name(name)).await
let (local_user, person, counts) = local_user::table
.filter(lower(person::name).eq(name.to_lowercase()))
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)
.await?;
Ok(Self {
local_user,
person,
counts,
})
} }
pub async fn find_by_email_or_name( pub async fn find_by_email_or_name(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
name_or_email: &str, name_or_email: &str,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; queries()
let (local_user, person, counts) = local_user::table .read(pool, ReadBy::NameOrEmail(name_or_email))
.inner_join(person::table) .await
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.filter(
lower(person::name)
.eq(lower(name_or_email))
.or(local_user::email.eq(name_or_email)),
)
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)
.await?;
Ok(Self {
local_user,
person,
counts,
})
} }
pub async fn find_by_email(pool: &mut DbPool<'_>, from_email: &str) -> Result<Self, Error> { pub async fn find_by_email(pool: &mut DbPool<'_>, from_email: &str) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; queries().read(pool, ReadBy::Email(from_email)).await
let (local_user, person, counts) = local_user::table
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.filter(local_user::email.eq(from_email))
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.first::<LocalUserViewTuple>(conn)
.await?;
Ok(Self {
local_user,
person,
counts,
})
} }
pub async fn list_admins_with_emails(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> { pub async fn list_admins_with_emails(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, ListMode::AdminsWithEmails).await
let res = local_user::table
.filter(person::admin.eq(true))
.filter(local_user::email.is_not_null())
.inner_join(person::table)
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
.select((
local_user::all_columns,
person::all_columns,
person_aggregates::all_columns,
))
.load::<LocalUserViewTuple>(conn)
.await?;
Ok(res.into_iter().map(LocalUserView::from_tuple).collect())
} }
} }

View file

@ -1,5 +1,6 @@
use crate::structs::PostReportView; use crate::structs::PostReportView;
use diesel::{ use diesel::{
pg::Pg,
result::Error, result::Error,
BoolExpressionMethods, BoolExpressionMethods,
ExpressionMethods, ExpressionMethods,
@ -10,6 +11,7 @@ use diesel::{
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::PostAggregates, aggregates::structs::PostAggregates,
aliases,
newtypes::{CommunityId, PersonId, PostReportId}, newtypes::{CommunityId, PersonId, PostReportId},
schema::{ schema::{
community, community,
@ -28,7 +30,7 @@ use lemmy_db_schema::{
post_report::PostReport, post_report::PostReport,
}, },
traits::JoinView, traits::JoinView,
utils::{get_conn, limit_and_offset, DbPool}, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
}; };
type PostReportViewTuple = ( type PostReportViewTuple = (
@ -43,34 +45,16 @@ type PostReportViewTuple = (
Option<Person>, Option<Person>,
); );
impl PostReportView { fn queries<'a>() -> Queries<
/// returns the PostReportView for the provided report_id impl ReadFn<'a, PostReportView, (PostReportId, PersonId)>,
/// impl ListFn<'a, PostReportView, (PostReportQuery, &'a Person)>,
/// * `report_id` - the report id to obtain > {
pub async fn read( let all_joins = |query: post_report::BoxedQuery<'a, Pg>, my_person_id: PersonId| {
pool: &mut DbPool<'_>, query
report_id: PostReportId,
my_person_id: PersonId,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let (person_alias_1, person_alias_2) = diesel::alias!(person as person1, person as person2);
let (
post_report,
post,
community,
creator,
post_creator,
creator_banned_from_community,
post_like,
counts,
resolver,
) = post_report::table
.find(report_id)
.inner_join(post::table) .inner_join(post::table)
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person::table.on(post_report::creator_id.eq(person::id))) .inner_join(person::table.on(post_report::creator_id.eq(person::id)))
.inner_join(person_alias_1.on(post::creator_id.eq(person_alias_1.field(person::id)))) .inner_join(aliases::person1.on(post::creator_id.eq(aliases::person1.field(person::id))))
.left_join( .left_join(
community_person_ban::table.on( community_person_ban::table.on(
post::community_id post::community_id
@ -87,35 +71,79 @@ impl PostReportView {
) )
.inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)))
.left_join( .left_join(
person_alias_2.on(post_report::resolver_id.eq(person_alias_2.field(person::id).nullable())), aliases::person2
.on(post_report::resolver_id.eq(aliases::person2.field(person::id).nullable())),
) )
.select(( .select((
post_report::all_columns, post_report::all_columns,
post::all_columns, post::all_columns,
community::all_columns, community::all_columns,
person::all_columns, person::all_columns,
person_alias_1.fields(person::all_columns), aliases::person1.fields(person::all_columns),
community_person_ban::all_columns.nullable(), community_person_ban::all_columns.nullable(),
post_like::score.nullable(), post_like::score.nullable(),
post_aggregates::all_columns, post_aggregates::all_columns,
person_alias_2.fields(person::all_columns.nullable()), aliases::person2.fields(person::all_columns.nullable()),
)) ))
.first::<PostReportViewTuple>(conn) };
.await?;
let my_vote = post_like; let read = move |mut conn: DbConn<'a>, (report_id, my_person_id): (PostReportId, PersonId)| async move {
all_joins(
post_report::table.find(report_id).into_boxed(),
my_person_id,
)
.first::<PostReportViewTuple>(&mut conn)
.await
};
Ok(Self { let list = move |mut conn: DbConn<'a>, (options, my_person): (PostReportQuery, &'a Person)| async move {
post_report, let mut query = all_joins(post_report::table.into_boxed(), my_person.id);
post,
community, if let Some(community_id) = options.community_id {
creator, query = query.filter(post::community_id.eq(community_id));
post_creator, }
creator_banned_from_community: creator_banned_from_community.is_some(),
my_vote, if options.unresolved_only.unwrap_or(false) {
counts, query = query.filter(post_report::resolved.eq(false));
resolver, }
})
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
query = query
.order_by(post_report::published.desc())
.limit(limit)
.offset(offset);
// If its not an admin, get only the ones you mod
if !my_person.admin {
query
.inner_join(
community_moderator::table.on(
community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(my_person.id)),
),
)
.load::<PostReportViewTuple>(&mut conn)
.await
} else {
query.load::<PostReportViewTuple>(&mut conn).await
}
};
Queries::new(read, list)
}
impl PostReportView {
/// returns the PostReportView for the provided report_id
///
/// * `report_id` - the report id to obtain
pub async fn read(
pool: &mut DbPool<'_>,
report_id: PostReportId,
my_person_id: PersonId,
) -> Result<Self, Error> {
queries().read(pool, (report_id, my_person_id)).await
} }
/// returns the current unresolved post report count for the communities you mod /// returns the current unresolved post report count for the communities you mod
@ -172,77 +200,7 @@ impl PostReportQuery {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
my_person: &Person, my_person: &Person,
) -> Result<Vec<PostReportView>, Error> { ) -> Result<Vec<PostReportView>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, (self, my_person)).await
let (person_alias_1, person_alias_2) = diesel::alias!(person as person1, person as person2);
let mut query = post_report::table
.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(person_alias_1.on(post::creator_id.eq(person_alias_1.field(person::id))))
.left_join(
community_person_ban::table.on(
post::community_id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(post::creator_id)),
),
)
.left_join(
post_like::table.on(
post::id
.eq(post_like::post_id)
.and(post_like::person_id.eq(my_person.id)),
),
)
.inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)))
.left_join(
person_alias_2.on(post_report::resolver_id.eq(person_alias_2.field(person::id).nullable())),
)
.select((
post_report::all_columns,
post::all_columns,
community::all_columns,
person::all_columns,
person_alias_1.fields(person::all_columns),
community_person_ban::all_columns.nullable(),
post_like::score.nullable(),
post_aggregates::all_columns,
person_alias_2.fields(person::all_columns.nullable()),
))
.into_boxed();
if let Some(community_id) = self.community_id {
query = query.filter(post::community_id.eq(community_id));
}
if self.unresolved_only.unwrap_or(false) {
query = query.filter(post_report::resolved.eq(false));
}
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
query = query
.order_by(post_report::published.desc())
.limit(limit)
.offset(offset);
// If its not an admin, get only the ones you mod
let res = if !my_person.admin {
query
.inner_join(
community_moderator::table.on(
community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(my_person.id)),
),
)
.load::<PostReportViewTuple>(conn)
.await?
} else {
query.load::<PostReportViewTuple>(conn).await?
};
Ok(res.into_iter().map(PostReportView::from_tuple).collect())
} }
} }

View file

@ -40,7 +40,7 @@ use lemmy_db_schema::{
post::{Post, PostRead, PostSaved}, post::{Post, PostRead, PostSaved},
}, },
traits::JoinView, traits::JoinView,
utils::{fuzzy_search, get_conn, limit_and_offset, DbPool}, utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
ListingType, ListingType,
SortType, SortType,
}; };
@ -62,19 +62,15 @@ type PostViewTuple = (
sql_function!(fn coalesce(x: sql_types::Nullable<sql_types::BigInt>, y: sql_types::BigInt) -> sql_types::BigInt); sql_function!(fn coalesce(x: sql_types::Nullable<sql_types::BigInt>, y: sql_types::BigInt) -> sql_types::BigInt);
impl PostView { fn queries<'a>() -> Queries<
pub async fn read( impl ReadFn<'a, PostView, (PostId, Option<PersonId>, Option<bool>)>,
pool: &mut DbPool<'_>, impl ListFn<'a, PostView, PostQuery<'a>>,
post_id: PostId, > {
my_person_id: Option<PersonId>, let all_joins = |query: post_aggregates::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| {
is_mod_or_admin: Option<bool>,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
// The left join below will return None in this case // The left join below will return None in this case
let person_id_join = my_person_id.unwrap_or(PersonId(-1)); let person_id_join = my_person_id.unwrap_or(PersonId(-1));
let mut query = post_aggregates::table
.filter(post_aggregates::post_id.eq(post_id)) query
.inner_join(person::table) .inner_join(person::table)
.inner_join(community::table) .inner_join(community::table)
.left_join( .left_join(
@ -134,23 +130,41 @@ impl PostView {
.and(person_post_aggregates::person_id.eq(person_id_join)), .and(person_post_aggregates::person_id.eq(person_id_join)),
), ),
) )
.select(( };
post::all_columns,
person::all_columns, let selection = (
community::all_columns, post::all_columns,
community_person_ban::all_columns.nullable(), person::all_columns,
post_aggregates::all_columns, community::all_columns,
community_follower::all_columns.nullable(), community_person_ban::all_columns.nullable(),
post_saved::all_columns.nullable(), post_aggregates::all_columns,
post_read::all_columns.nullable(), community_follower::all_columns.nullable(),
person_block::all_columns.nullable(), post_saved::all_columns.nullable(),
post_like::score.nullable(), post_read::all_columns.nullable(),
coalesce( person_block::all_columns.nullable(),
post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(), post_like::score.nullable(),
post_aggregates::comments, coalesce(
), post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(),
)) post_aggregates::comments,
.into_boxed(); ),
);
let read = move |mut conn: DbConn<'a>,
(post_id, my_person_id, is_mod_or_admin): (
PostId,
Option<PersonId>,
Option<bool>,
)| async move {
// The left join below will return None in this case
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,
)
.select(selection);
// Hide deleted and removed for non-admins or mods // Hide deleted and removed for non-admins or mods
if !is_mod_or_admin.unwrap_or(false) { if !is_mod_or_admin.unwrap_or(false) {
@ -170,117 +184,18 @@ impl PostView {
); );
} }
let ( query.first::<PostViewTuple>(&mut conn).await
post, };
creator,
community,
creator_banned_from_community,
counts,
follower,
saved,
read,
creator_blocked,
post_like,
unread_comments,
) = query.first::<PostViewTuple>(conn).await?;
// If a person is given, then my_vote, if None, should be 0, not null let list = move |mut conn: DbConn<'a>, options: PostQuery<'a>| async move {
// Necessary to differentiate between other person's votes let person_id = options.local_user.map(|l| l.person.id);
let my_vote = if my_person_id.is_some() && post_like.is_none() { let local_user_id = options.local_user.map(|l| l.local_user.id);
Some(0)
} else {
post_like
};
Ok(PostView {
post,
creator,
community,
creator_banned_from_community: creator_banned_from_community.is_some(),
counts,
subscribed: CommunityFollower::to_subscribed_type(&follower),
saved: saved.is_some(),
read: read.is_some(),
creator_blocked: creator_blocked.is_some(),
my_vote,
unread_comments,
})
}
}
#[derive(Default)]
pub struct PostQuery<'a> {
pub listing_type: Option<ListingType>,
pub sort: Option<SortType>,
pub creator_id: Option<PersonId>,
pub community_id: Option<CommunityId>,
pub local_user: Option<&'a LocalUserView>,
pub search_term: Option<String>,
pub url_search: Option<String>,
pub saved_only: Option<bool>,
pub moderator_view: Option<bool>,
pub is_profile_view: Option<bool>,
pub page: Option<i64>,
pub limit: Option<i64>,
}
impl<'a> PostQuery<'a> {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PostView>, Error> {
let conn = &mut get_conn(pool).await?;
// The left join below will return None in this case // The left join below will return None in this case
let person_id_join = self.local_user.map(|l| l.person.id).unwrap_or(PersonId(-1)); let person_id_join = person_id.unwrap_or(PersonId(-1));
let local_user_id_join = self let local_user_id_join = local_user_id.unwrap_or(LocalUserId(-1));
.local_user
.map(|l| l.local_user.id)
.unwrap_or(LocalUserId(-1));
let mut query = post_aggregates::table let mut query = all_joins(post_aggregates::table.into_boxed(), person_id)
.inner_join(person::table)
.inner_join(post::table)
.inner_join(community::table)
.left_join(
community_person_ban::table.on(
post_aggregates::community_id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(post_aggregates::creator_id)),
),
)
.left_join(
community_follower::table.on(
post_aggregates::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id_join)),
),
)
.left_join(
community_moderator::table.on(
post::community_id
.eq(community_moderator::community_id)
.and(community_moderator::person_id.eq(person_id_join)),
),
)
.left_join(
post_saved::table.on(
post_aggregates::post_id
.eq(post_saved::post_id)
.and(post_saved::person_id.eq(person_id_join)),
),
)
.left_join(
post_read::table.on(
post_aggregates::post_id
.eq(post_read::post_id)
.and(post_read::person_id.eq(person_id_join)),
),
)
.left_join(
person_block::table.on(
post_aggregates::creator_id
.eq(person_block::target_id)
.and(person_block::person_id.eq(person_id_join)),
),
)
.left_join( .left_join(
community_block::table.on( community_block::table.on(
post_aggregates::community_id post_aggregates::community_id
@ -288,20 +203,6 @@ impl<'a> PostQuery<'a> {
.and(community_block::person_id.eq(person_id_join)), .and(community_block::person_id.eq(person_id_join)),
), ),
) )
.left_join(
post_like::table.on(
post_aggregates::post_id
.eq(post_like::post_id)
.and(post_like::person_id.eq(person_id_join)),
),
)
.left_join(
person_post_aggregates::table.on(
post_aggregates::post_id
.eq(person_post_aggregates::post_id)
.and(person_post_aggregates::person_id.eq(person_id_join)),
),
)
.left_join( .left_join(
local_user_language::table.on( local_user_language::table.on(
post::language_id post::language_id
@ -309,26 +210,10 @@ impl<'a> PostQuery<'a> {
.and(local_user_language::local_user_id.eq(local_user_id_join)), .and(local_user_language::local_user_id.eq(local_user_id_join)),
), ),
) )
.select(( .select(selection);
post::all_columns,
person::all_columns,
community::all_columns,
community_person_ban::all_columns.nullable(),
post_aggregates::all_columns,
community_follower::all_columns.nullable(),
post_saved::all_columns.nullable(),
post_read::all_columns.nullable(),
person_block::all_columns.nullable(),
post_like::score.nullable(),
coalesce(
post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(),
post_aggregates::comments,
),
))
.into_boxed();
let is_profile_view = self.is_profile_view.unwrap_or(false); let is_profile_view = options.is_profile_view.unwrap_or(false);
let is_creator = self.creator_id == self.local_user.map(|l| l.person.id); let is_creator = options.creator_id == options.local_user.map(|l| l.person.id);
// only show deleted posts to creator // only show deleted posts to creator
if is_creator { if is_creator {
query = query query = query
@ -336,7 +221,7 @@ impl<'a> PostQuery<'a> {
.filter(post::deleted.eq(false)); .filter(post::deleted.eq(false));
} }
let is_admin = self.local_user.map(|l| l.person.admin).unwrap_or(false); let is_admin = options.local_user.map(|l| l.person.admin).unwrap_or(false);
// only show removed posts to admin when viewing user profile // only show removed posts to admin when viewing user profile
if !(is_profile_view && is_admin) { if !(is_profile_view && is_admin) {
query = query query = query
@ -344,19 +229,19 @@ impl<'a> PostQuery<'a> {
.filter(post::removed.eq(false)); .filter(post::removed.eq(false));
} }
if self.community_id.is_none() { if options.community_id.is_none() {
query = query.then_order_by(post_aggregates::featured_local.desc()); query = query.then_order_by(post_aggregates::featured_local.desc());
} else if let Some(community_id) = self.community_id { } else if let Some(community_id) = options.community_id {
query = query query = query
.filter(post_aggregates::community_id.eq(community_id)) .filter(post_aggregates::community_id.eq(community_id))
.then_order_by(post_aggregates::featured_community.desc()); .then_order_by(post_aggregates::featured_community.desc());
} }
if let Some(creator_id) = self.creator_id { if let Some(creator_id) = options.creator_id {
query = query.filter(post_aggregates::creator_id.eq(creator_id)); query = query.filter(post_aggregates::creator_id.eq(creator_id));
} }
if let Some(listing_type) = self.listing_type { if let Some(listing_type) = options.listing_type {
match listing_type { match listing_type {
ListingType::Subscribed => { ListingType::Subscribed => {
query = query.filter(community_follower::person_id.is_not_null()) query = query.filter(community_follower::person_id.is_not_null())
@ -378,11 +263,11 @@ impl<'a> PostQuery<'a> {
} }
} }
if let Some(url_search) = self.url_search { if let Some(url_search) = options.url_search {
query = query.filter(post::url.eq(url_search)); query = query.filter(post::url.eq(url_search));
} }
if let Some(search_term) = self.search_term { if let Some(search_term) = options.search_term {
let searcher = fuzzy_search(&search_term); let searcher = fuzzy_search(&search_term);
query = query.filter( query = query.filter(
post::name post::name
@ -391,7 +276,7 @@ impl<'a> PostQuery<'a> {
); );
} }
if !self if !options
.local_user .local_user
.map(|l| l.local_user.show_nsfw) .map(|l| l.local_user.show_nsfw)
.unwrap_or(false) .unwrap_or(false)
@ -401,7 +286,7 @@ impl<'a> PostQuery<'a> {
.filter(community::nsfw.eq(false)); .filter(community::nsfw.eq(false));
}; };
if !self if !options
.local_user .local_user
.map(|l| l.local_user.show_bot_accounts) .map(|l| l.local_user.show_bot_accounts)
.unwrap_or(true) .unwrap_or(true)
@ -409,16 +294,16 @@ impl<'a> PostQuery<'a> {
query = query.filter(person::bot_account.eq(false)); query = query.filter(person::bot_account.eq(false));
}; };
if self.saved_only.unwrap_or(false) { if options.saved_only.unwrap_or(false) {
query = query.filter(post_saved::post_id.is_not_null()); query = query.filter(post_saved::post_id.is_not_null());
} }
if self.moderator_view.unwrap_or(false) { if options.moderator_view.unwrap_or(false) {
query = query.filter(community_moderator::person_id.is_not_null()); query = query.filter(community_moderator::person_id.is_not_null());
} }
// Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read // Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read
// setting wont be able to see saved posts. // setting wont be able to see saved posts.
else if !self else if !options
.local_user .local_user
.map(|l| l.local_user.show_read_posts) .map(|l| l.local_user.show_read_posts)
.unwrap_or(true) .unwrap_or(true)
@ -426,18 +311,18 @@ impl<'a> PostQuery<'a> {
query = query.filter(post_read::post_id.is_null()); query = query.filter(post_read::post_id.is_null());
} }
if self.local_user.is_some() { if options.local_user.is_some() {
// Filter out the rows with missing languages // Filter out the rows with missing languages
query = query.filter(local_user_language::language_id.is_not_null()); query = query.filter(local_user_language::language_id.is_not_null());
// Don't show blocked communities or persons // Don't show blocked communities or persons
query = query.filter(community_block::person_id.is_null()); query = query.filter(community_block::person_id.is_null());
if !self.moderator_view.unwrap_or(false) { if !options.moderator_view.unwrap_or(false) {
query = query.filter(person_block::person_id.is_null()); query = query.filter(person_block::person_id.is_null());
} }
} }
query = match self.sort.unwrap_or(SortType::Hot) { query = match options.sort.unwrap_or(SortType::Hot) {
SortType::Active => query SortType::Active => query
.then_order_by(post_aggregates::hot_rank_active.desc()) .then_order_by(post_aggregates::hot_rank_active.desc())
.then_order_by(post_aggregates::published.desc()), .then_order_by(post_aggregates::published.desc()),
@ -496,15 +381,58 @@ impl<'a> PostQuery<'a> {
.then_order_by(post_aggregates::published.desc()), .then_order_by(post_aggregates::published.desc()),
}; };
let (limit, offset) = limit_and_offset(self.page, self.limit)?; let (limit, offset) = limit_and_offset(options.page, options.limit)?;
query = query.limit(limit).offset(offset); query = query.limit(limit).offset(offset);
debug!("Post View Query: {:?}", debug_query::<Pg, _>(&query)); debug!("Post View Query: {:?}", debug_query::<Pg, _>(&query));
let res = query.load::<PostViewTuple>(conn).await?; query.load::<PostViewTuple>(&mut conn).await
};
Ok(res.into_iter().map(PostView::from_tuple).collect()) Queries::new(read, list)
}
impl PostView {
pub async fn read(
pool: &mut DbPool<'_>,
post_id: PostId,
my_person_id: Option<PersonId>,
is_mod_or_admin: Option<bool>,
) -> Result<Self, Error> {
let mut res = queries()
.read(pool, (post_id, my_person_id, is_mod_or_admin))
.await?;
// If a person is given, then my_vote, if None, should be 0, not null
// Necessary to differentiate between other person's votes
if my_person_id.is_some() && res.my_vote.is_none() {
res.my_vote = Some(0)
};
Ok(res)
}
}
#[derive(Default)]
pub struct PostQuery<'a> {
pub listing_type: Option<ListingType>,
pub sort: Option<SortType>,
pub creator_id: Option<PersonId>,
pub community_id: Option<CommunityId>,
pub local_user: Option<&'a LocalUserView>,
pub search_term: Option<String>,
pub url_search: Option<String>,
pub saved_only: Option<bool>,
pub moderator_view: Option<bool>,
pub is_profile_view: Option<bool>,
pub page: Option<i64>,
pub limit: Option<i64>,
}
impl<'a> PostQuery<'a> {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PostView>, Error> {
queries().list(pool, self).await
} }
} }

View file

@ -1,7 +1,15 @@
use crate::structs::PrivateMessageReportView; use crate::structs::PrivateMessageReportView;
use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl}; use diesel::{
pg::Pg,
result::Error,
ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases,
newtypes::PrivateMessageReportId, newtypes::PrivateMessageReportId,
schema::{person, private_message, private_message_report}, schema::{person, private_message, private_message_report},
source::{ source::{
@ -10,7 +18,7 @@ use lemmy_db_schema::{
private_message_report::PrivateMessageReport, private_message_report::PrivateMessageReport,
}, },
traits::JoinView, traits::JoinView,
utils::{get_conn, limit_and_offset, DbPool}, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
}; };
type PrivateMessageReportViewTuple = ( type PrivateMessageReportViewTuple = (
@ -21,6 +29,57 @@ type PrivateMessageReportViewTuple = (
Option<Person>, Option<Person>,
); );
fn queries<'a>() -> Queries<
impl ReadFn<'a, PrivateMessageReportView, PrivateMessageReportId>,
impl ListFn<'a, PrivateMessageReportView, PrivateMessageReportQuery>,
> {
let all_joins =
|query: private_message_report::BoxedQuery<'a, Pg>| {
query
.inner_join(private_message::table)
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
.inner_join(
aliases::person1
.on(private_message_report::creator_id.eq(aliases::person1.field(person::id))),
)
.left_join(aliases::person2.on(
private_message_report::resolver_id.eq(aliases::person2.field(person::id).nullable()),
))
.select((
private_message_report::all_columns,
private_message::all_columns,
person::all_columns,
aliases::person1.fields(person::all_columns),
aliases::person2.fields(person::all_columns).nullable(),
))
};
let read = move |mut conn: DbConn<'a>, report_id: PrivateMessageReportId| async move {
all_joins(private_message_report::table.find(report_id).into_boxed())
.first::<PrivateMessageReportViewTuple>(&mut conn)
.await
};
let list = move |mut conn: DbConn<'a>, options: PrivateMessageReportQuery| async move {
let mut query = all_joins(private_message_report::table.into_boxed());
if options.unresolved_only.unwrap_or(false) {
query = query.filter(private_message_report::resolved.eq(false));
}
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
query
.order_by(private_message::published.desc())
.limit(limit)
.offset(offset)
.load::<PrivateMessageReportViewTuple>(&mut conn)
.await
};
Queries::new(read, list)
}
impl PrivateMessageReportView { impl PrivateMessageReportView {
/// returns the PrivateMessageReportView for the provided report_id /// returns the PrivateMessageReportView for the provided report_id
/// ///
@ -29,40 +88,7 @@ impl PrivateMessageReportView {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
report_id: PrivateMessageReportId, report_id: PrivateMessageReportId,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; queries().read(pool, report_id).await
let (person_alias_1, person_alias_2) = diesel::alias!(person as person1, person as person2);
let (private_message_report, private_message, private_message_creator, creator, resolver) =
private_message_report::table
.find(report_id)
.inner_join(private_message::table)
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
.inner_join(
person_alias_1
.on(private_message_report::creator_id.eq(person_alias_1.field(person::id))),
)
.left_join(
person_alias_2.on(
private_message_report::resolver_id.eq(person_alias_2.field(person::id).nullable()),
),
)
.select((
private_message_report::all_columns,
private_message::all_columns,
person::all_columns,
person_alias_1.fields(person::all_columns),
person_alias_2.fields(person::all_columns).nullable(),
))
.first::<PrivateMessageReportViewTuple>(conn)
.await?;
Ok(Self {
private_message_report,
private_message,
private_message_creator,
creator,
resolver,
})
} }
/// Returns the current unresolved post report count for the communities you mod /// Returns the current unresolved post report count for the communities you mod
@ -89,47 +115,7 @@ pub struct PrivateMessageReportQuery {
impl PrivateMessageReportQuery { impl PrivateMessageReportQuery {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PrivateMessageReportView>, Error> { pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PrivateMessageReportView>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, self).await
let (person_alias_1, person_alias_2) = diesel::alias!(person as person1, person as person2);
let mut query = private_message_report::table
.inner_join(private_message::table)
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
.inner_join(
person_alias_1.on(private_message_report::creator_id.eq(person_alias_1.field(person::id))),
)
.left_join(
person_alias_2
.on(private_message_report::resolver_id.eq(person_alias_2.field(person::id).nullable())),
)
.select((
private_message_report::all_columns,
private_message::all_columns,
person::all_columns,
person_alias_1.fields(person::all_columns),
person_alias_2.fields(person::all_columns).nullable(),
))
.into_boxed();
if self.unresolved_only.unwrap_or(false) {
query = query.filter(private_message_report::resolved.eq(false));
}
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
query = query
.order_by(private_message::published.desc())
.limit(limit)
.offset(offset);
let res = query.load::<PrivateMessageReportViewTuple>(conn).await?;
Ok(
res
.into_iter()
.map(PrivateMessageReportView::from_tuple)
.collect(),
)
} }
} }

View file

@ -10,44 +10,87 @@ use diesel::{
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases,
newtypes::{PersonId, PrivateMessageId}, newtypes::{PersonId, PrivateMessageId},
schema::{person, private_message}, schema::{person, private_message},
source::{person::Person, private_message::PrivateMessage}, source::{person::Person, private_message::PrivateMessage},
traits::JoinView, traits::JoinView,
utils::{get_conn, limit_and_offset, DbPool}, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
}; };
use tracing::debug; use tracing::debug;
type PrivateMessageViewTuple = (PrivateMessage, Person, Person); type PrivateMessageViewTuple = (PrivateMessage, Person, Person);
fn queries<'a>() -> Queries<
impl ReadFn<'a, PrivateMessageView, PrivateMessageId>,
impl ListFn<'a, PrivateMessageView, (PrivateMessageQuery, PersonId)>,
> {
let all_joins = |query: private_message::BoxedQuery<'a, Pg>| {
query
.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))),
)
};
let selection = (
private_message::all_columns,
person::all_columns,
aliases::person1.fields(person::all_columns),
);
let read = move |mut conn: DbConn<'a>, private_message_id: PrivateMessageId| async move {
all_joins(private_message::table.find(private_message_id).into_boxed())
.order_by(private_message::published.desc())
.select(selection)
.first::<PrivateMessageViewTuple>(&mut conn)
.await
};
let list = move |mut conn: DbConn<'a>,
(options, recipient_id): (PrivateMessageQuery, PersonId)| async move {
let mut query = all_joins(private_message::table.into_boxed()).select(selection);
// If its unread, I only want the ones to me
if options.unread_only.unwrap_or(false) {
query = query
.filter(private_message::read.eq(false))
.filter(private_message::recipient_id.eq(recipient_id));
}
// Otherwise, I want the ALL view to show both sent and received
else {
query = query.filter(
private_message::recipient_id
.eq(recipient_id)
.or(private_message::creator_id.eq(recipient_id)),
)
}
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
query = query
.filter(private_message::deleted.eq(false))
.limit(limit)
.offset(offset)
.order_by(private_message::published.desc());
debug!(
"Private Message View Query: {:?}",
debug_query::<Pg, _>(&query)
);
query.load::<PrivateMessageViewTuple>(&mut conn).await
};
Queries::new(read, list)
}
impl PrivateMessageView { impl PrivateMessageView {
pub async fn read( pub async fn read(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
private_message_id: PrivateMessageId, private_message_id: PrivateMessageId,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; queries().read(pool, private_message_id).await
let person_alias_1 = diesel::alias!(person as person1);
let (private_message, creator, recipient) = private_message::table
.find(private_message_id)
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
.inner_join(
person_alias_1.on(private_message::recipient_id.eq(person_alias_1.field(person::id))),
)
.order_by(private_message::published.desc())
.select((
private_message::all_columns,
person::all_columns,
person_alias_1.fields(person::all_columns),
))
.first::<PrivateMessageViewTuple>(conn)
.await?;
Ok(PrivateMessageView {
private_message,
creator,
recipient,
})
} }
/// Gets the number of unread messages /// Gets the number of unread messages
@ -80,57 +123,7 @@ impl PrivateMessageQuery {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
recipient_id: PersonId, recipient_id: PersonId,
) -> Result<Vec<PrivateMessageView>, Error> { ) -> Result<Vec<PrivateMessageView>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, (self, recipient_id)).await
let person_alias_1 = diesel::alias!(person as person1);
let mut query = private_message::table
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
.inner_join(
person_alias_1.on(private_message::recipient_id.eq(person_alias_1.field(person::id))),
)
.select((
private_message::all_columns,
person::all_columns,
person_alias_1.fields(person::all_columns),
))
.into_boxed();
// If its unread, I only want the ones to me
if self.unread_only.unwrap_or(false) {
query = query
.filter(private_message::read.eq(false))
.filter(private_message::recipient_id.eq(recipient_id));
}
// Otherwise, I want the ALL view to show both sent and received
else {
query = query.filter(
private_message::recipient_id
.eq(recipient_id)
.or(private_message::creator_id.eq(recipient_id)),
)
}
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
query = query
.filter(private_message::deleted.eq(false))
.limit(limit)
.offset(offset)
.order_by(private_message::published.desc());
debug!(
"Private Message View Query: {:?}",
debug_query::<Pg, _>(&query)
);
let res = query.load::<PrivateMessageViewTuple>(conn).await?;
Ok(
res
.into_iter()
.map(PrivateMessageView::from_tuple)
.collect(),
)
} }
} }

View file

@ -1,6 +1,7 @@
use crate::structs::RegistrationApplicationView; use crate::structs::RegistrationApplicationView;
use diesel::{ use diesel::{
dsl::count, dsl::count,
pg::Pg,
result::Error, result::Error,
ExpressionMethods, ExpressionMethods,
JoinOnDsl, JoinOnDsl,
@ -9,6 +10,7 @@ use diesel::{
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases,
schema::{local_user, person, registration_application}, schema::{local_user, person, registration_application},
source::{ source::{
local_user::LocalUser, local_user::LocalUser,
@ -16,47 +18,75 @@ use lemmy_db_schema::{
registration_application::RegistrationApplication, registration_application::RegistrationApplication,
}, },
traits::JoinView, traits::JoinView,
utils::{get_conn, limit_and_offset, DbPool}, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
}; };
type RegistrationApplicationViewTuple = type RegistrationApplicationViewTuple =
(RegistrationApplication, LocalUser, Person, Option<Person>); (RegistrationApplication, LocalUser, Person, Option<Person>);
fn queries<'a>() -> Queries<
impl ReadFn<'a, RegistrationApplicationView, i32>,
impl ListFn<'a, RegistrationApplicationView, RegistrationApplicationQuery>,
> {
let all_joins = |query: registration_application::BoxedQuery<'a, Pg>| {
query
.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>, registration_application_id: i32| async move {
all_joins(
registration_application::table
.find(registration_application_id)
.into_boxed(),
)
.first::<RegistrationApplicationViewTuple>(&mut conn)
.await
};
let list = move |mut conn: DbConn<'a>, options: RegistrationApplicationQuery| async move {
let mut query = all_joins(registration_application::table.into_boxed());
if options.unread_only.unwrap_or(false) {
query = query.filter(registration_application::admin_id.is_null())
}
if options.verified_email_only.unwrap_or(false) {
query = query.filter(local_user::email_verified.eq(true))
}
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
query = query
.limit(limit)
.offset(offset)
.order_by(registration_application::published.desc());
query
.load::<RegistrationApplicationViewTuple>(&mut conn)
.await
};
Queries::new(read, list)
}
impl RegistrationApplicationView { impl RegistrationApplicationView {
pub async fn read( pub async fn read(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
registration_application_id: i32, registration_application_id: i32,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; queries().read(pool, registration_application_id).await
let person_alias_1 = diesel::alias!(person as person1);
let (registration_application, creator_local_user, creator, admin) =
registration_application::table
.find(registration_application_id)
.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())),
)
.order_by(registration_application::published.desc())
.select((
registration_application::all_columns,
local_user::all_columns,
person::all_columns,
person_alias_1.fields(person::all_columns).nullable(),
))
.first::<RegistrationApplicationViewTuple>(conn)
.await?;
Ok(RegistrationApplicationView {
registration_application,
creator_local_user,
creator,
admin,
})
} }
/// Returns the current unread registration_application count /// Returns the current unread registration_application count
@ -101,48 +131,7 @@ impl RegistrationApplicationQuery {
self, self,
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
) -> Result<Vec<RegistrationApplicationView>, Error> { ) -> Result<Vec<RegistrationApplicationView>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, self).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())),
)
.order_by(registration_application::published.desc())
.select((
registration_application::all_columns,
local_user::all_columns,
person::all_columns,
person_alias_1.fields(person::all_columns).nullable(),
))
.into_boxed();
if self.unread_only.unwrap_or(false) {
query = query.filter(registration_application::admin_id.is_null())
}
if self.verified_email_only.unwrap_or(false) {
query = query.filter(local_user::email_verified.eq(true))
}
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
query = query
.limit(limit)
.offset(offset)
.order_by(registration_application::published.desc());
let res = query.load::<RegistrationApplicationViewTuple>(conn).await?;
Ok(
res
.into_iter()
.map(RegistrationApplicationView::from_tuple)
.collect(),
)
} }
} }

View file

@ -1,5 +1,6 @@
use crate::structs::CommentReplyView; use crate::structs::CommentReplyView;
use diesel::{ use diesel::{
pg::Pg,
result::Error, result::Error,
BoolExpressionMethods, BoolExpressionMethods,
ExpressionMethods, ExpressionMethods,
@ -10,6 +11,7 @@ use diesel::{
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::CommentAggregates, aggregates::structs::CommentAggregates,
aliases,
newtypes::{CommentReplyId, PersonId}, newtypes::{CommentReplyId, PersonId},
schema::{ schema::{
comment, comment,
@ -33,7 +35,7 @@ use lemmy_db_schema::{
post::Post, post::Post,
}, },
traits::JoinView, traits::JoinView,
utils::{get_conn, limit_and_offset, DbPool}, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
CommentSortType, CommentSortType,
}; };
@ -52,38 +54,20 @@ type CommentReplyViewTuple = (
Option<i16>, Option<i16>,
); );
impl CommentReplyView { fn queries<'a>() -> Queries<
pub async fn read( impl ReadFn<'a, CommentReplyView, (CommentReplyId, Option<PersonId>)>,
pool: &mut DbPool<'_>, impl ListFn<'a, CommentReplyView, CommentReplyQuery>,
comment_reply_id: CommentReplyId, > {
my_person_id: Option<PersonId>, let all_joins = |query: comment_reply::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| {
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let person_alias_1 = diesel::alias!(person as person1);
// The left join below will return None in this case // The left join below will return None in this case
let person_id_join = my_person_id.unwrap_or(PersonId(-1)); let person_id_join = my_person_id.unwrap_or(PersonId(-1));
let ( query
comment_reply,
comment,
creator,
post,
community,
recipient,
counts,
creator_banned_from_community,
follower,
saved,
creator_blocked,
my_vote,
) = comment_reply::table
.find(comment_reply_id)
.inner_join(comment::table) .inner_join(comment::table)
.inner_join(person::table.on(comment::creator_id.eq(person::id))) .inner_join(person::table.on(comment::creator_id.eq(person::id)))
.inner_join(post::table.on(comment::post_id.eq(post::id))) .inner_join(post::table.on(comment::post_id.eq(post::id)))
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person_alias_1) .inner_join(aliases::person1)
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
.left_join( .left_join(
community_person_ban::table.on( community_person_ban::table.on(
@ -126,7 +110,7 @@ impl CommentReplyView {
person::all_columns, person::all_columns,
post::all_columns, post::all_columns,
community::all_columns, community::all_columns,
person_alias_1.fields(person::all_columns), aliases::person1.fields(person::all_columns),
comment_aggregates::all_columns, comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(), community_person_ban::all_columns.nullable(),
community_follower::all_columns.nullable(), community_follower::all_columns.nullable(),
@ -134,23 +118,63 @@ impl CommentReplyView {
person_block::all_columns.nullable(), person_block::all_columns.nullable(),
comment_like::score.nullable(), comment_like::score.nullable(),
)) ))
.first::<CommentReplyViewTuple>(conn) };
.await?;
Ok(CommentReplyView { let read =
comment_reply, move |mut conn: DbConn<'a>,
comment, (comment_reply_id, my_person_id): (CommentReplyId, Option<PersonId>)| async move {
creator, all_joins(
post, comment_reply::table.find(comment_reply_id).into_boxed(),
community, my_person_id,
recipient, )
counts, .first::<CommentReplyViewTuple>(&mut conn)
creator_banned_from_community: creator_banned_from_community.is_some(), .await
subscribed: CommunityFollower::to_subscribed_type(&follower), };
saved: saved.is_some(),
creator_blocked: creator_blocked.is_some(), let list = move |mut conn: DbConn<'a>, options: CommentReplyQuery| async move {
my_vote, let mut query = all_joins(comment_reply::table.into_boxed(), options.my_person_id);
})
if let Some(recipient_id) = options.recipient_id {
query = query.filter(comment_reply::recipient_id.eq(recipient_id));
}
if options.unread_only.unwrap_or(false) {
query = query.filter(comment_reply::read.eq(false));
}
if !options.show_bot_accounts.unwrap_or(true) {
query = query.filter(person::bot_account.eq(false));
};
query = match options.sort.unwrap_or(CommentSortType::New) {
CommentSortType::Hot => query.then_order_by(comment_aggregates::hot_rank.desc()),
CommentSortType::Controversial => {
query.then_order_by(comment_aggregates::controversy_rank.desc())
}
CommentSortType::New => query.then_order_by(comment_reply::published.desc()),
CommentSortType::Old => query.then_order_by(comment_reply::published.asc()),
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
};
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
query
.limit(limit)
.offset(offset)
.load::<CommentReplyViewTuple>(&mut conn)
.await
};
Queries::new(read, list)
}
impl CommentReplyView {
pub async fn read(
pool: &mut DbPool<'_>,
comment_reply_id: CommentReplyId,
my_person_id: Option<PersonId>,
) -> Result<Self, Error> {
queries().read(pool, (comment_reply_id, my_person_id)).await
} }
/// Gets the number of unread replies /// Gets the number of unread replies
@ -187,102 +211,7 @@ pub struct CommentReplyQuery {
impl CommentReplyQuery { impl CommentReplyQuery {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<CommentReplyView>, Error> { pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<CommentReplyView>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, self).await
let person_alias_1 = diesel::alias!(person as person1);
// The left join below will return None in this case
let person_id_join = self.my_person_id.unwrap_or(PersonId(-1));
let mut query = comment_reply::table
.inner_join(comment::table)
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
.inner_join(post::table.on(comment::post_id.eq(post::id)))
.inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person_alias_1)
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
)
.left_join(
community_follower::table.on(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id_join)),
),
)
.left_join(
comment_saved::table.on(
comment::id
.eq(comment_saved::comment_id)
.and(comment_saved::person_id.eq(person_id_join)),
),
)
.left_join(
person_block::table.on(
comment::creator_id
.eq(person_block::target_id)
.and(person_block::person_id.eq(person_id_join)),
),
)
.left_join(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(person_id_join)),
),
)
.select((
comment_reply::all_columns,
comment::all_columns,
person::all_columns,
post::all_columns,
community::all_columns,
person_alias_1.fields(person::all_columns),
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
community_follower::all_columns.nullable(),
comment_saved::all_columns.nullable(),
person_block::all_columns.nullable(),
comment_like::score.nullable(),
))
.into_boxed();
if let Some(recipient_id) = self.recipient_id {
query = query.filter(comment_reply::recipient_id.eq(recipient_id));
}
if self.unread_only.unwrap_or(false) {
query = query.filter(comment_reply::read.eq(false));
}
if !self.show_bot_accounts.unwrap_or(true) {
query = query.filter(person::bot_account.eq(false));
};
query = match self.sort.unwrap_or(CommentSortType::New) {
CommentSortType::Hot => query.then_order_by(comment_aggregates::hot_rank.desc()),
CommentSortType::Controversial => {
query.then_order_by(comment_aggregates::controversy_rank.desc())
}
CommentSortType::New => query.then_order_by(comment_reply::published.desc()),
CommentSortType::Old => query.then_order_by(comment_reply::published.asc()),
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
};
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
let res = query
.limit(limit)
.offset(offset)
.load::<CommentReplyViewTuple>(conn)
.await?;
Ok(res.into_iter().map(CommentReplyView::from_tuple).collect())
} }
} }

View file

@ -1,5 +1,6 @@
use crate::structs::{CommunityModeratorView, CommunityView, PersonView}; use crate::structs::{CommunityModeratorView, CommunityView, PersonView};
use diesel::{ use diesel::{
pg::Pg,
result::Error, result::Error,
BoolExpressionMethods, BoolExpressionMethods,
ExpressionMethods, ExpressionMethods,
@ -19,7 +20,7 @@ use lemmy_db_schema::{
local_user::LocalUser, local_user::LocalUser,
}, },
traits::JoinView, traits::JoinView,
utils::{fuzzy_search, get_conn, limit_and_offset, DbPool}, utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
ListingType, ListingType,
SortType, SortType,
}; };
@ -31,19 +32,15 @@ type CommunityViewTuple = (
Option<CommunityBlock>, Option<CommunityBlock>,
); );
impl CommunityView { fn queries<'a>() -> Queries<
pub async fn read( impl ReadFn<'a, CommunityView, (CommunityId, Option<PersonId>, Option<bool>)>,
pool: &mut DbPool<'_>, impl ListFn<'a, CommunityView, CommunityQuery<'a>>,
community_id: CommunityId, > {
my_person_id: Option<PersonId>, let all_joins = |query: community::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| {
is_mod_or_admin: Option<bool>,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
// The left join below will return None in this case // The left join below will return None in this case
let person_id_join = my_person_id.unwrap_or(PersonId(-1)); let person_id_join = my_person_id.unwrap_or(PersonId(-1));
let mut query = community::table query
.find(community_id)
.inner_join(community_aggregates::table) .inner_join(community_aggregates::table)
.left_join( .left_join(
community_follower::table.on( community_follower::table.on(
@ -59,29 +56,126 @@ impl CommunityView {
.and(community_block::person_id.eq(person_id_join)), .and(community_block::person_id.eq(person_id_join)),
), ),
) )
.select(( };
community::all_columns,
community_aggregates::all_columns, let selection = (
community_follower::all_columns.nullable(), community::all_columns,
community_block::all_columns.nullable(), community_aggregates::all_columns,
)) community_follower::all_columns.nullable(),
.into_boxed(); community_block::all_columns.nullable(),
);
let not_removed_or_deleted = community::removed
.eq(false)
.and(community::deleted.eq(false));
let read = move |mut conn: DbConn<'a>,
(community_id, my_person_id, is_mod_or_admin): (
CommunityId,
Option<PersonId>,
Option<bool>,
)| async move {
let mut query = all_joins(
community::table.find(community_id).into_boxed(),
my_person_id,
)
.select(selection);
// Hide deleted and removed for non-admins or mods // Hide deleted and removed for non-admins or mods
if !is_mod_or_admin.unwrap_or(false) { if !is_mod_or_admin.unwrap_or(false) {
query = query query = query.filter(not_removed_or_deleted);
.filter(community::removed.eq(false))
.filter(community::deleted.eq(false));
} }
let (community, counts, follower, blocked) = query.first::<CommunityViewTuple>(conn).await?; query.first::<CommunityViewTuple>(&mut conn).await
};
Ok(CommunityView { let list = move |mut conn: DbConn<'a>, options: CommunityQuery<'a>| async move {
community, use SortType::*;
subscribed: CommunityFollower::to_subscribed_type(&follower),
blocked: blocked.is_some(), let my_person_id = options.local_user.map(|l| l.person_id);
counts,
}) // The left join below will return None in this case
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
let mut query = all_joins(community::table.into_boxed(), my_person_id)
.left_join(local_user::table.on(local_user::person_id.eq(person_id_join)))
.select(selection);
if let Some(search_term) = options.search_term {
let searcher = fuzzy_search(&search_term);
query = query
.filter(community::name.ilike(searcher.clone()))
.or_filter(community::title.ilike(searcher))
}
// Hide deleted and removed for non-admins or mods
if !options.is_mod_or_admin.unwrap_or(false) {
query = query.filter(not_removed_or_deleted).filter(
community::hidden
.eq(false)
.or(community_follower::person_id.eq(person_id_join)),
);
}
match options.sort.unwrap_or(Hot) {
Hot | Active => 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()),
};
if let Some(listing_type) = options.listing_type {
query = match listing_type {
ListingType::Subscribed => query.filter(community_follower::person_id.is_not_null()), // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
ListingType::Local => query.filter(community::local.eq(true)),
_ => query,
};
}
// Don't show blocked communities or nsfw communities if not enabled in profile
if options.local_user.is_some() {
query = query.filter(community_block::person_id.is_null());
query = query.filter(community::nsfw.eq(false).or(local_user::show_nsfw.eq(true)));
} else {
// No person in request, only show nsfw communities if show_nsfw is passed into request
if !options.show_nsfw.unwrap_or(false) {
query = query.filter(community::nsfw.eq(false));
}
}
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
query
.limit(limit)
.offset(offset)
.load::<CommunityViewTuple>(&mut conn)
.await
};
Queries::new(read, list)
}
impl CommunityView {
pub async fn read(
pool: &mut DbPool<'_>,
community_id: CommunityId,
my_person_id: Option<PersonId>,
is_mod_or_admin: Option<bool>,
) -> Result<Self, Error> {
queries()
.read(pool, (community_id, my_person_id, is_mod_or_admin))
.await
} }
pub async fn is_mod_or_admin( pub async fn is_mod_or_admin(
@ -113,102 +207,7 @@ pub struct CommunityQuery<'a> {
impl<'a> CommunityQuery<'a> { impl<'a> CommunityQuery<'a> {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<CommunityView>, Error> { pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<CommunityView>, Error> {
use SortType::*; queries().list(pool, self).await
let conn = &mut get_conn(pool).await?;
// The left join below will return None in this case
let person_id_join = self.local_user.map(|l| l.person_id).unwrap_or(PersonId(-1));
let mut query = community::table
.inner_join(community_aggregates::table)
.left_join(local_user::table.on(local_user::person_id.eq(person_id_join)))
.left_join(
community_follower::table.on(
community::id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id_join)),
),
)
.left_join(
community_block::table.on(
community::id
.eq(community_block::community_id)
.and(community_block::person_id.eq(person_id_join)),
),
)
.select((
community::all_columns,
community_aggregates::all_columns,
community_follower::all_columns.nullable(),
community_block::all_columns.nullable(),
))
.into_boxed();
if let Some(search_term) = self.search_term {
let searcher = fuzzy_search(&search_term);
query = query
.filter(community::name.ilike(searcher.clone()))
.or_filter(community::title.ilike(searcher));
};
// Hide deleted and removed for non-admins or mods
if !self.is_mod_or_admin.unwrap_or(false) {
query = query
.filter(community::removed.eq(false))
.filter(community::deleted.eq(false))
.filter(
community::hidden
.eq(false)
.or(community_follower::person_id.eq(person_id_join)),
);
}
match self.sort.unwrap_or(Hot) {
Hot | Active => 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()),
};
if let Some(listing_type) = self.listing_type {
query = match listing_type {
ListingType::Subscribed => query.filter(community_follower::person_id.is_not_null()), // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
ListingType::Local => query.filter(community::local.eq(true)),
_ => query,
};
}
// Don't show blocked communities or nsfw communities if not enabled in profile
if self.local_user.is_some() {
query = query.filter(community_block::person_id.is_null());
query = query.filter(community::nsfw.eq(false).or(local_user::show_nsfw.eq(true)));
} else {
// No person in request, only show nsfw communities if show_nsfw is passed into request
if !self.show_nsfw.unwrap_or(false) {
query = query.filter(community::nsfw.eq(false));
}
}
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
let res = query
.limit(limit)
.offset(offset)
.load::<CommunityViewTuple>(conn)
.await?;
Ok(res.into_iter().map(CommunityView::from_tuple).collect())
} }
} }

View file

@ -1,6 +1,7 @@
use crate::structs::PersonMentionView; use crate::structs::PersonMentionView;
use diesel::{ use diesel::{
dsl::now, dsl::now,
pg::Pg,
result::Error, result::Error,
BoolExpressionMethods, BoolExpressionMethods,
ExpressionMethods, ExpressionMethods,
@ -11,6 +12,7 @@ use diesel::{
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::CommentAggregates, aggregates::structs::CommentAggregates,
aliases,
newtypes::{PersonId, PersonMentionId}, newtypes::{PersonId, PersonMentionId},
schema::{ schema::{
comment, comment,
@ -34,7 +36,7 @@ use lemmy_db_schema::{
post::Post, post::Post,
}, },
traits::JoinView, traits::JoinView,
utils::{get_conn, limit_and_offset, DbPool}, utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
CommentSortType, CommentSortType,
}; };
@ -53,46 +55,21 @@ type PersonMentionViewTuple = (
Option<i16>, Option<i16>,
); );
impl PersonMentionView { fn queries<'a>() -> Queries<
pub async fn read( impl ReadFn<'a, PersonMentionView, (PersonMentionId, Option<PersonId>)>,
pool: &mut DbPool<'_>, impl ListFn<'a, PersonMentionView, PersonMentionQuery>,
person_mention_id: PersonMentionId, > {
my_person_id: Option<PersonId>, let all_joins = |query: person_mention::BoxedQuery<'a, Pg>, my_person_id: Option<PersonId>| {
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let person_alias_1 = diesel::alias!(person as person1);
// The left join below will return None in this case // The left join below will return None in this case
let person_id_join = my_person_id.unwrap_or(PersonId(-1)); let person_id_join = my_person_id.unwrap_or(PersonId(-1));
let ( query
person_mention,
comment,
creator,
post,
community,
recipient,
counts,
creator_banned_from_community,
follower,
saved,
creator_blocked,
my_vote,
) = person_mention::table
.find(person_mention_id)
.inner_join(comment::table) .inner_join(comment::table)
.inner_join(person::table.on(comment::creator_id.eq(person::id))) .inner_join(person::table.on(comment::creator_id.eq(person::id)))
.inner_join(post::table.on(comment::post_id.eq(post::id))) .inner_join(post::table.on(comment::post_id.eq(post::id)))
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person_alias_1) .inner_join(aliases::person1)
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
)
.left_join( .left_join(
community_follower::table.on( community_follower::table.on(
post::community_id post::community_id
@ -121,37 +98,101 @@ impl PersonMentionView {
.and(comment_like::person_id.eq(person_id_join)), .and(comment_like::person_id.eq(person_id_join)),
), ),
) )
.select(( };
person_mention::all_columns,
comment::all_columns,
person::all_columns,
post::all_columns,
community::all_columns,
person_alias_1.fields(person::all_columns),
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
community_follower::all_columns.nullable(),
comment_saved::all_columns.nullable(),
person_block::all_columns.nullable(),
comment_like::score.nullable(),
))
.first::<PersonMentionViewTuple>(conn)
.await?;
Ok(PersonMentionView { let selection = (
person_mention, person_mention::all_columns,
comment, comment::all_columns,
creator, person::all_columns,
post, post::all_columns,
community, community::all_columns,
recipient, aliases::person1.fields(person::all_columns),
counts, comment_aggregates::all_columns,
creator_banned_from_community: creator_banned_from_community.is_some(), community_person_ban::all_columns.nullable(),
subscribed: CommunityFollower::to_subscribed_type(&follower), community_follower::all_columns.nullable(),
saved: saved.is_some(), comment_saved::all_columns.nullable(),
creator_blocked: creator_blocked.is_some(), person_block::all_columns.nullable(),
my_vote, comment_like::score.nullable(),
}) );
let read =
move |mut conn: DbConn<'a>,
(person_mention_id, my_person_id): (PersonMentionId, Option<PersonId>)| async move {
all_joins(
person_mention::table.find(person_mention_id).into_boxed(),
my_person_id,
)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
)
.select(selection)
.first::<PersonMentionViewTuple>(&mut conn)
.await
};
let list = move |mut conn: DbConn<'a>, options: PersonMentionQuery| async move {
let mut query = all_joins(person_mention::table.into_boxed(), options.my_person_id)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id))
.and(
community_person_ban::expires
.is_null()
.or(community_person_ban::expires.gt(now)),
),
),
)
.select(selection);
if let Some(recipient_id) = options.recipient_id {
query = query.filter(person_mention::recipient_id.eq(recipient_id));
}
if options.unread_only.unwrap_or(false) {
query = query.filter(person_mention::read.eq(false));
}
if !options.show_bot_accounts.unwrap_or(true) {
query = query.filter(person::bot_account.eq(false));
};
query = match options.sort.unwrap_or(CommentSortType::Hot) {
CommentSortType::Hot => query.then_order_by(comment_aggregates::hot_rank.desc()),
CommentSortType::Controversial => {
query.then_order_by(comment_aggregates::controversy_rank.desc())
}
CommentSortType::New => query.then_order_by(comment::published.desc()),
CommentSortType::Old => query.then_order_by(comment::published.asc()),
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
};
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
query
.limit(limit)
.offset(offset)
.load::<PersonMentionViewTuple>(&mut conn)
.await
};
Queries::new(read, list)
}
impl PersonMentionView {
pub async fn read(
pool: &mut DbPool<'_>,
person_mention_id: PersonMentionId,
my_person_id: Option<PersonId>,
) -> Result<Self, Error> {
queries()
.read(pool, (person_mention_id, my_person_id))
.await
} }
/// Gets the number of unread mentions /// Gets the number of unread mentions
@ -187,107 +228,7 @@ pub struct PersonMentionQuery {
impl PersonMentionQuery { impl PersonMentionQuery {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonMentionView>, Error> { pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonMentionView>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, self).await
let person_alias_1 = diesel::alias!(person as person1);
// The left join below will return None in this case
let person_id_join = self.my_person_id.unwrap_or(PersonId(-1));
let mut query = person_mention::table
.inner_join(comment::table)
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
.inner_join(post::table.on(comment::post_id.eq(post::id)))
.inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person_alias_1)
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id))
.and(
community_person_ban::expires
.is_null()
.or(community_person_ban::expires.gt(now)),
),
),
)
.left_join(
community_follower::table.on(
post::community_id
.eq(community_follower::community_id)
.and(community_follower::person_id.eq(person_id_join)),
),
)
.left_join(
comment_saved::table.on(
comment::id
.eq(comment_saved::comment_id)
.and(comment_saved::person_id.eq(person_id_join)),
),
)
.left_join(
person_block::table.on(
comment::creator_id
.eq(person_block::target_id)
.and(person_block::person_id.eq(person_id_join)),
),
)
.left_join(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(person_id_join)),
),
)
.select((
person_mention::all_columns,
comment::all_columns,
person::all_columns,
post::all_columns,
community::all_columns,
person_alias_1.fields(person::all_columns),
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
community_follower::all_columns.nullable(),
comment_saved::all_columns.nullable(),
person_block::all_columns.nullable(),
comment_like::score.nullable(),
))
.into_boxed();
if let Some(recipient_id) = self.recipient_id {
query = query.filter(person_mention::recipient_id.eq(recipient_id));
}
if self.unread_only.unwrap_or(false) {
query = query.filter(person_mention::read.eq(false));
}
if !self.show_bot_accounts.unwrap_or(true) {
query = query.filter(person::bot_account.eq(false));
};
query = match self.sort.unwrap_or(CommentSortType::Hot) {
CommentSortType::Hot => query.then_order_by(comment_aggregates::hot_rank.desc()),
CommentSortType::Controversial => {
query.then_order_by(comment_aggregates::controversy_rank.desc())
}
CommentSortType::New => query.then_order_by(comment::published.desc()),
CommentSortType::Old => query.then_order_by(comment::published.asc()),
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
};
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
let res = query
.limit(limit)
.offset(offset)
.load::<PersonMentionViewTuple>(conn)
.await?;
Ok(res.into_iter().map(PersonMentionView::from_tuple).collect())
} }
} }

View file

@ -1,6 +1,7 @@
use crate::structs::PersonView; use crate::structs::PersonView;
use diesel::{ use diesel::{
dsl::now, dsl::now,
pg::Pg,
result::Error, result::Error,
BoolExpressionMethods, BoolExpressionMethods,
ExpressionMethods, ExpressionMethods,
@ -15,23 +16,82 @@ use lemmy_db_schema::{
schema::{person, person_aggregates}, schema::{person, person_aggregates},
source::person::Person, source::person::Person,
traits::JoinView, traits::JoinView,
utils::{fuzzy_search, get_conn, limit_and_offset, DbPool}, utils::{fuzzy_search, get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn},
PersonSortType, PersonSortType,
}; };
use std::iter::Iterator;
type PersonViewTuple = (Person, PersonAggregates); type PersonViewTuple = (Person, PersonAggregates);
impl PersonView { enum ListMode {
pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Self, Error> { Admins,
let conn = &mut get_conn(pool).await?; Banned,
let res = person::table Query(PersonQuery),
.find(person_id) }
fn queries<'a>(
) -> Queries<impl ReadFn<'a, PersonView, PersonId>, impl ListFn<'a, PersonView, ListMode>> {
let all_joins = |query: person::BoxedQuery<'a, Pg>| {
query
.inner_join(person_aggregates::table) .inner_join(person_aggregates::table)
.select((person::all_columns, person_aggregates::all_columns)) .select((person::all_columns, person_aggregates::all_columns))
.first::<PersonViewTuple>(conn) };
.await?;
Ok(Self::from_tuple(res)) let read = move |mut conn: DbConn<'a>, person_id: PersonId| async move {
all_joins(person::table.find(person_id).into_boxed())
.first::<PersonViewTuple>(&mut conn)
.await
};
let list = move |mut conn: DbConn<'a>, mode: ListMode| async move {
let mut query = all_joins(person::table.into_boxed());
match mode {
ListMode::Admins => {
query = query
.filter(person::admin.eq(true))
.filter(person::deleted.eq(false))
.order_by(person::published);
}
ListMode::Banned => {
query = query
.filter(
person::banned.eq(true).and(
person::ban_expires
.is_null()
.or(person::ban_expires.gt(now)),
),
)
.filter(person::deleted.eq(false));
}
ListMode::Query(options) => {
if let Some(search_term) = options.search_term {
let searcher = fuzzy_search(&search_term);
query = query
.filter(person::name.ilike(searcher.clone()))
.or_filter(person::display_name.ilike(searcher));
}
query = match options.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(options.page, options.limit)?;
query = query.limit(limit).offset(offset);
}
}
query.load::<PersonViewTuple>(&mut conn).await
};
Queries::new(read, list)
}
impl PersonView {
pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<Self, Error> {
queries().read(pool, person_id).await
} }
pub async fn is_admin(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<bool, Error> { pub async fn is_admin(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<bool, Error> {
@ -44,37 +104,13 @@ impl PersonView {
.await?; .await?;
Ok(is_admin) Ok(is_admin)
} }
pub async fn admins(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?;
let admins = person::table
.inner_join(person_aggregates::table)
.select((person::all_columns, person_aggregates::all_columns))
.filter(person::admin.eq(true))
.filter(person::deleted.eq(false))
.order_by(person::published)
.load::<PersonViewTuple>(conn)
.await?;
Ok(admins.into_iter().map(Self::from_tuple).collect()) pub async fn admins(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
queries().list(pool, ListMode::Admins).await
} }
pub async fn banned(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> { pub async fn banned(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, ListMode::Banned).await
let banned = person::table
.inner_join(person_aggregates::table)
.select((person::all_columns, person_aggregates::all_columns))
.filter(
person::banned.eq(true).and(
person::ban_expires
.is_null()
.or(person::ban_expires.gt(now)),
),
)
.filter(person::deleted.eq(false))
.load::<PersonViewTuple>(conn)
.await?;
Ok(banned.into_iter().map(Self::from_tuple).collect())
} }
} }
@ -88,34 +124,7 @@ pub struct PersonQuery {
impl PersonQuery { impl PersonQuery {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonView>, Error> { pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonView>, Error> {
let conn = &mut get_conn(pool).await?; queries().list(pool, ListMode::Query(self)).await
let mut query = person::table
.inner_join(person_aggregates::table)
.select((person::all_columns, person_aggregates::all_columns))
.into_boxed();
if let Some(search_term) = self.search_term {
let searcher = fuzzy_search(&search_term);
query = query
.filter(person::name.ilike(searcher.clone()))
.or_filter(person::display_name.ilike(searcher));
}
query = match self.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(self.page, self.limit)?;
query = query.limit(limit).offset(offset);
let res = query.load::<PersonViewTuple>(conn).await?;
Ok(res.into_iter().map(PersonView::from_tuple).collect())
} }
} }