Adding a can_mod field to CommentView, PostView, and CommunityView. (#5398)

* Adding a can_mod field to CommentView, PostView, and CommunityView.

- Also removes the moderators from GetPost, as that should no longer
  be necessary.
- Fixes #4365

* Adding can_mod to reply variants.

* Addressing PR comments.

* Hiding comment.content for non-admins / mods.

- Uses the local_user_can_mod function now.
- Fixes #5232
This commit is contained in:
Dessalines
2025-02-11 06:09:27 -05:00
committed by GitHub
parent 426684d90f
commit 91a77ae456
18 changed files with 619 additions and 327 deletions

View File

@@ -4,13 +4,7 @@ use lemmy_db_schema::{
PostFeatureType,
PostSortType,
};
use lemmy_db_views::structs::{
CommunityModeratorView,
CommunityView,
PaginationCursor,
PostView,
VoteView,
};
use lemmy_db_views::structs::{CommunityView, PaginationCursor, PostView, VoteView};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
@@ -77,7 +71,6 @@ pub struct GetPost {
pub struct GetPostResponse {
pub post_view: PostView,
pub community_view: CommunityView,
pub moderators: Vec<CommunityModeratorView>,
/// A list of cross-posts, or other times / communities this link has been posted to.
pub cross_posts: Vec<PostView>,
}

View File

@@ -13,7 +13,7 @@ use lemmy_db_schema::{
};
use lemmy_db_views::{
post::post_view::PostQuery,
structs::{CommunityModeratorView, CommunityView, LocalUserView, PostView, SiteView},
structs::{CommunityView, LocalUserView, PostView, SiteView},
};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
@@ -84,8 +84,6 @@ pub async fn get_post(
)
.await?;
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
// Fetch the cross_posts
let cross_posts = if let Some(url) = &post_view.post.url {
let mut x_posts = PostQuery {
@@ -108,7 +106,6 @@ pub async fn get_post(
Ok(Json(GetPostResponse {
post_view,
community_view,
moderators,
cross_posts,
}))
}

View File

@@ -1,5 +1,6 @@
use crate::{
diesel::{DecoratableTarget, OptionalExtension},
impls::local_user::local_user_can_mod,
newtypes::{CommentId, DbUrl, PersonId},
schema::{comment, comment_actions},
source::comment::{
@@ -16,9 +17,10 @@ use crate::{
};
use chrono::{DateTime, Utc};
use diesel::{
dsl::insert_into,
dsl::{case_when, insert_into, not},
expression::SelectableHelper,
result::Error,
BoolExpressionMethods,
ExpressionMethods,
NullableExpressionMethods,
QueryDsl,
@@ -124,6 +126,33 @@ impl Comment {
}
}
/// Selects the comment columns, but gives an empty string for content when
/// deleted or removed, and you're not a mod/admin.
#[diesel::dsl::auto_type]
pub fn comment_select_remove_deletes() -> _ {
let deleted_or_removed = comment::deleted.or(comment::removed);
// You can only view the content if it hasn't been removed, or you can mod.
let can_view_content = not(deleted_or_removed).or(local_user_can_mod());
let content = case_when(can_view_content, comment::content).otherwise("");
(
comment::id,
comment::creator_id,
comment::post_id,
content,
comment::removed,
comment::published,
comment::updated,
comment::deleted,
comment::ap_id,
comment::local,
comment::path,
comment::distinguished,
comment::language_id,
)
}
#[async_trait]
impl Crud for Comment {
type InsertForm = CommentInsertForm;

View File

@@ -33,7 +33,7 @@ use crate::{
use chrono::{DateTime, Utc};
use diesel::{
deserialize,
dsl::{self, exists, insert_into, not},
dsl::{exists, insert_into, not},
expression::SelectableHelper,
pg::Pg,
result::Error,
@@ -398,10 +398,6 @@ impl Bannable for CommunityPersonBan {
}
impl CommunityFollower {
pub fn select_subscribed_type() -> dsl::Nullable<community_actions::follow_state> {
community_actions::follow_state.nullable()
}
/// Check if a remote instance has any followers on local instance. For this it is enough to check
/// if any follow relation is stored. Dont use this for local community.
pub async fn check_has_local_followers(
@@ -440,6 +436,14 @@ impl CommunityFollower {
}
}
// TODO
// I'd really like to have these on the impl, but unfortunately they have to be top level,
// according to https://diesel.rs/guides/composing-applications.html
#[diesel::dsl::auto_type]
pub fn community_follower_select_subscribed_type() -> _ {
community_actions::follow_state.nullable()
}
impl Queryable<sql_types::Nullable<crate::schema::sql_types::CommunityFollowerState>, Pg>
for SubscribedType
{

View File

@@ -1,4 +1,5 @@
use crate::{
aliases::creator_community_actions,
newtypes::{CommunityId, DbUrl, LanguageId, LocalUserId, PersonId},
schema::{community, community_actions, local_user, person, registration_application},
source::{
@@ -19,9 +20,12 @@ use bcrypt::{hash, DEFAULT_COST};
use diesel::{
dsl::{insert_into, not, IntervalDsl},
result::Error,
BoolExpressionMethods,
CombineDsl,
ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods,
PgExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;
@@ -295,6 +299,29 @@ impl LocalUser {
}
}
// TODO
// I'd really like to have these on the impl, but unfortunately they have to be top level,
// according to https://diesel.rs/guides/composing-applications.html
/// Checks to see if you can mod an item.
///
/// Caveat: Since admin status isn't federated or ordered, it can't know whether
/// item creator is a federated admin, or a higher admin.
/// The back-end will reject an action for admin that is higher via
/// LocalUser::is_higher_mod_or_admin_check
#[diesel::dsl::auto_type]
pub fn local_user_can_mod() -> _ {
let am_admin = local_user::admin.nullable();
let creator_became_moderator = creator_community_actions
.field(community_actions::became_moderator)
.nullable();
let am_higher_mod = community_actions::became_moderator
.nullable()
.le(creator_became_moderator);
am_admin.or(am_higher_mod).is_not_distinct_from(true)
}
/// Adds some helper functions for an optional LocalUser
pub trait LocalUserOptionHelper {
fn person_id(&self) -> Option<PersonId>;

View File

@@ -23,9 +23,10 @@ pub mod sensitive;
pub mod schema;
#[cfg(feature = "full")]
pub mod aliases {
use crate::schema::{community_actions, person};
use crate::schema::{community_actions, local_user, person};
diesel::alias!(
community_actions as creator_community_actions: CreatorCommunityActions,
local_user as creator_local_user: CreatorLocalUser,
person as person1: Person1,
person as person2: Person2,
);

View File

@@ -20,7 +20,8 @@ use diesel::{
use diesel_async::RunQueryDsl;
use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::{self, creator_community_actions},
aliases::{self, creator_community_actions, creator_local_user},
impls::{community::community_follower_select_subscribed_type, local_user::local_user_can_mod},
newtypes::PersonId,
schema::{
comment,
@@ -44,10 +45,7 @@ use lemmy_db_schema::{
private_message,
tag,
},
source::{
combined::inbox::{inbox_combined_keys as key, InboxCombined},
community::CommunityFollower,
},
source::combined::inbox::{inbox_combined_keys as key, InboxCombined},
traits::InternalToCombinedView,
utils::{functions::coalesce, get_conn, DbPool},
InboxDataType,
@@ -98,10 +96,12 @@ impl InboxCombinedViewInternal {
let community_join = post::community_id.eq(community::id);
let local_user_join = local_user::table.on(
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(my_person_id));
let creator_local_user_join = creator_local_user.on(
item_creator
.eq(local_user::person_id)
.and(local_user::admin.eq(true)),
.eq(creator_local_user.field(local_user::person_id))
.and(creator_local_user.field(local_user::admin).eq(true)),
);
let post_aggregates_join = post_aggregates::table.on(post::id.eq(post_aggregates::post_id));
@@ -167,6 +167,7 @@ impl InboxCombinedViewInternal {
.left_join(comment_aggregates_join)
.left_join(creator_community_actions_join)
.left_join(local_user_join)
.left_join(creator_local_user_join)
.left_join(community_actions_join)
.left_join(instance_actions_join)
.left_join(post_actions_join)
@@ -307,10 +308,13 @@ impl InboxCombinedQuery {
comment_aggregates::all_columns.nullable(),
comment_actions::saved.nullable(),
comment_actions::like_score.nullable(),
CommunityFollower::select_subscribed_type(),
community_follower_select_subscribed_type(),
person::all_columns,
aliases::person1.fields(person::all_columns),
local_user::admin.nullable().is_not_null(),
creator_local_user
.field(local_user::admin)
.nullable()
.is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()
@@ -321,6 +325,7 @@ impl InboxCombinedQuery {
.is_not_null(),
person_actions::blocked.nullable().is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
local_user_can_mod(),
))
.into_boxed();
@@ -447,6 +452,7 @@ impl InternalToCombinedView for InboxCombinedViewInternal {
saved: v.comment_saved,
my_vote: v.my_comment_vote,
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
}))
} else if let (
Some(person_comment_mention),
@@ -478,6 +484,7 @@ impl InternalToCombinedView for InboxCombinedViewInternal {
saved: v.comment_saved,
my_vote: v.my_comment_vote,
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
},
))
} else if let (
@@ -513,6 +520,7 @@ impl InternalToCombinedView for InboxCombinedViewInternal {
image_details: v.image_details,
post_tags: v.post_tags,
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
}))
} else if let Some(private_message) = v.private_message {
Some(InboxCombinedView::PrivateMessage(PrivateMessageView {

View File

@@ -18,7 +18,8 @@ use diesel::{
use diesel_async::RunQueryDsl;
use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::creator_community_actions,
aliases::{creator_community_actions, creator_local_user},
impls::{community::community_follower_select_subscribed_type, local_user::local_user_can_mod},
newtypes::PersonId,
schema::{
comment,
@@ -38,10 +39,7 @@ use lemmy_db_schema::{
post_tag,
tag,
},
source::{
combined::person_content::{person_content_combined_keys as key, PersonContentCombined},
community::CommunityFollower,
},
source::combined::person_content::{person_content_combined_keys as key, PersonContentCombined},
traits::InternalToCombinedView,
utils::{functions::coalesce, get_conn, DbPool},
PersonContentType,
@@ -86,11 +84,12 @@ impl PersonContentCombinedViewInternal {
.eq(item_creator),
),
);
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(my_person_id));
let local_user_join = local_user::table.on(
let creator_local_user_join = creator_local_user.on(
item_creator
.eq(local_user::person_id)
.and(local_user::admin.eq(true)),
.eq(creator_local_user.field(local_user::person_id))
.and(creator_local_user.field(local_user::admin).eq(true)),
);
let community_actions_join = community_actions::table.on(
@@ -132,6 +131,7 @@ impl PersonContentCombinedViewInternal {
.inner_join(community_join)
.left_join(creator_community_actions_join)
.left_join(local_user_join)
.left_join(creator_local_user_join)
.left_join(community_actions_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
@@ -179,10 +179,12 @@ impl PersonContentCombinedViewInternal {
),
);
let local_user_join = local_user::table.on(
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(my_person_id));
let creator_local_user_join = creator_local_user.on(
item_creator
.eq(local_user::person_id)
.and(local_user::admin.eq(true)),
.eq(creator_local_user.field(local_user::person_id))
.and(creator_local_user.field(local_user::admin).eq(true)),
);
let community_actions_join = community_actions::table.on(
@@ -224,6 +226,7 @@ impl PersonContentCombinedViewInternal {
.inner_join(community_join)
.left_join(creator_community_actions_join)
.left_join(local_user_join)
.left_join(creator_local_user_join)
.left_join(community_actions_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
@@ -327,8 +330,11 @@ impl PersonContentCombinedQuery {
post::all_columns,
community::all_columns,
person::all_columns,
CommunityFollower::select_subscribed_type(),
local_user::admin.nullable().is_not_null(),
community_follower_select_subscribed_type(),
creator_local_user
.field(local_user::admin)
.nullable()
.is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()
@@ -339,6 +345,7 @@ impl PersonContentCombinedQuery {
.is_not_null(),
person_actions::blocked.nullable().is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
local_user_can_mod(),
))
.into_boxed();
@@ -404,6 +411,7 @@ impl InternalToCombinedView for PersonContentCombinedViewInternal {
saved: v.comment_saved,
my_vote: v.my_comment_vote,
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
}))
} else {
Some(PersonContentCombinedView::Post(PostView {
@@ -424,6 +432,7 @@ impl InternalToCombinedView for PersonContentCombinedViewInternal {
image_details: v.image_details,
banned_from_community: v.banned_from_community,
tags: v.post_tags,
can_mod: v.can_mod,
}))
}
}

View File

@@ -14,7 +14,8 @@ use diesel::{
use diesel_async::RunQueryDsl;
use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::creator_community_actions,
aliases::{creator_community_actions, creator_local_user},
impls::{community::community_follower_select_subscribed_type, local_user::local_user_can_mod},
schema::{
comment,
comment_actions,
@@ -32,10 +33,7 @@ use lemmy_db_schema::{
post_tag,
tag,
},
source::{
combined::person_saved::{person_saved_combined_keys as key, PersonSavedCombined},
community::CommunityFollower,
},
source::combined::person_saved::{person_saved_combined_keys as key, PersonSavedCombined},
traits::InternalToCombinedView,
utils::{functions::coalesce, get_conn, DbPool},
PersonContentType,
@@ -124,8 +122,11 @@ impl PersonSavedCombinedQuery {
post::all_columns,
community::all_columns,
person::all_columns,
CommunityFollower::select_subscribed_type(),
local_user::admin.nullable().is_not_null(),
community_follower_select_subscribed_type(),
creator_local_user
.field(local_user::admin)
.nullable()
.is_not_null(),
creator_community_actions
.field(community_actions::became_moderator)
.nullable()
@@ -136,6 +137,7 @@ impl PersonSavedCombinedQuery {
.is_not_null(),
person_actions::blocked.nullable().is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
local_user_can_mod(),
))
.into_boxed();

View File

@@ -22,6 +22,7 @@ use diesel_async::RunQueryDsl;
use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::{self, creator_community_actions},
impls::community::community_follower_select_subscribed_type,
newtypes::{CommunityId, PersonId, PostId},
schema::{
comment,
@@ -43,10 +44,7 @@ use lemmy_db_schema::{
private_message_report,
report_combined,
},
source::{
combined::report::{report_combined_keys as key, ReportCombined},
community::CommunityFollower,
},
source::combined::report::{report_combined_keys as key, ReportCombined},
traits::InternalToCombinedView,
utils::{functions::coalesce, get_conn, DbPool, ReverseTimestampKey},
ReportType,
@@ -301,7 +299,7 @@ impl ReportCombinedQuery {
person::all_columns,
aliases::person1.fields(person::all_columns.nullable()),
community::all_columns.nullable(),
CommunityFollower::select_subscribed_type(),
community_follower_select_subscribed_type(),
aliases::person2.fields(person::all_columns.nullable()),
local_user::admin.nullable().is_not_null(),
creator_community_actions

View File

@@ -22,7 +22,8 @@ use diesel::{
use diesel_async::RunQueryDsl;
use i_love_jesus::PaginatedQueryBuilder;
use lemmy_db_schema::{
aliases::creator_community_actions,
aliases::{creator_community_actions, creator_local_user},
impls::{community::community_follower_select_subscribed_type, local_user::local_user_can_mod},
newtypes::{CommunityId, PersonId},
schema::{
comment,
@@ -43,10 +44,7 @@ use lemmy_db_schema::{
search_combined,
tag,
},
source::{
combined::search::{search_combined_keys as key, SearchCombined},
community::CommunityFollower,
},
source::combined::search::{search_combined_keys as key, SearchCombined},
traits::InternalToCombinedView,
utils::{
functions::coalesce,
@@ -118,10 +116,13 @@ impl SearchCombinedViewInternal {
.eq(item_creator),
),
);
let local_user_join = local_user::table.on(
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(my_person_id));
let creator_local_user_join = creator_local_user.on(
item_creator
.eq(local_user::person_id)
.and(local_user::admin.eq(true)),
.eq(creator_local_user.field(local_user::person_id))
.and(creator_local_user.field(local_user::admin).eq(true)),
);
let community_actions_join = community_actions::table.on(
@@ -169,6 +170,7 @@ impl SearchCombinedViewInternal {
.left_join(community_join)
.left_join(creator_community_actions_join)
.left_join(local_user_join)
.left_join(creator_local_user_join)
.left_join(community_actions_join)
.left_join(post_actions_join)
.left_join(person_actions_join)
@@ -279,7 +281,7 @@ impl SearchCombinedQuery {
community::all_columns.nullable(),
community_aggregates::all_columns.nullable(),
community_actions::blocked.nullable().is_not_null(),
CommunityFollower::select_subscribed_type(),
community_follower_select_subscribed_type(),
// Person
person_aggregates::all_columns.nullable(),
// // Shared
@@ -295,6 +297,7 @@ impl SearchCombinedQuery {
.is_not_null(),
person_actions::blocked.nullable().is_not_null(),
community_actions::received_ban.nullable().is_not_null(),
local_user_can_mod(),
))
.into_boxed();
@@ -461,6 +464,7 @@ impl InternalToCombinedView for SearchCombinedViewInternal {
saved: v.comment_saved,
my_vote: v.my_comment_vote,
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
}))
} else if let (
Some(post),
@@ -493,6 +497,7 @@ impl InternalToCombinedView for SearchCombinedViewInternal {
image_details: v.image_details,
banned_from_community: v.banned_from_community,
tags: v.post_tags,
can_mod: v.can_mod,
}))
} else if let (Some(community), Some(counts)) = (v.community, v.community_counts) {
Some(SearchCombinedView::Community(CommunityView {
@@ -501,6 +506,7 @@ impl InternalToCombinedView for SearchCombinedViewInternal {
subscribed: v.subscribed,
blocked: v.community_blocked,
banned_from_community: v.banned_from_community,
can_mod: v.can_mod,
}))
} else if let (Some(person), Some(counts)) = (v.item_creator, v.item_creator_counts) {
Some(SearchCombinedView::Person(PersonView {

View File

@@ -1,12 +1,11 @@
use crate::structs::{CommentSlimView, CommentView};
use diesel::{
dsl::{exists, not},
dsl::exists,
result::Error,
BoolExpressionMethods,
ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods,
PgTextExpressionMethods,
QueryDsl,
SelectableHelper,
};
@@ -23,13 +22,14 @@ use lemmy_db_schema::{
community,
community_actions,
instance_actions,
local_user,
local_user_language,
person,
person_actions,
post,
},
source::{community::CommunityFollowerState, local_user::LocalUser, site::Site},
utils::{fuzzy_search, get_conn, limit_and_offset, now, seconds_to_pg_interval, DbPool},
utils::{get_conn, limit_and_offset, now, seconds_to_pg_interval, DbPool},
CommentSortType,
CommunityVisibility,
ListingType,
@@ -75,6 +75,8 @@ impl CommentView {
),
);
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(my_person_id));
comment::table
.inner_join(person::table)
.inner_join(post::table)
@@ -85,6 +87,7 @@ impl CommentView {
.left_join(person_actions_join)
.left_join(instance_actions_join)
.left_join(comment_creator_community_actions_join)
.left_join(local_user_join)
}
pub async fn read(
@@ -121,8 +124,7 @@ impl CommentView {
res.my_vote = Some(0);
}
let is_admin = my_local_user.map(|u| u.admin).unwrap_or(false);
Ok(handle_deleted(res, is_admin))
Ok(res)
}
pub fn map_to_slim(self) -> CommentSlimView {
@@ -138,6 +140,7 @@ impl CommentView {
saved: self.saved,
creator_blocked: self.creator_blocked,
my_vote: self.my_vote,
can_mod: self.can_mod,
}
}
}
@@ -152,7 +155,6 @@ pub struct CommentQuery<'a> {
pub parent_path: Option<Ltree>,
pub creator_id: Option<PersonId>,
pub local_user: Option<&'a LocalUser>,
pub search_term: Option<String>,
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub page: Option<i64>,
@@ -184,14 +186,6 @@ impl CommentQuery<'_> {
if let Some(parent_path) = o.parent_path.as_ref() {
query = query.filter(comment::path.contained_by(parent_path));
};
//filtering out removed and deleted comments from search
if let Some(search_term) = o.search_term {
query = query.filter(
comment::content
.ilike(fuzzy_search(&search_term))
.and(not(comment::removed.or(comment::deleted))),
);
};
if let Some(community_id) = o.community_id {
query = query.filter(post::community_id.eq(community_id));
@@ -330,26 +324,10 @@ impl CommentQuery<'_> {
.load::<CommentView>(conn)
.await?;
let is_admin = o.local_user.map(|u| u.admin).unwrap_or(false);
// Note: deleted and removed comments are done on the front side
Ok(
res
.into_iter()
.map(|c| handle_deleted(c, is_admin))
.collect(),
)
Ok(res)
}
}
/// Only show deleted / removed content for admins.
fn handle_deleted(mut c: CommentView, is_admin: bool) -> CommentView {
if !is_admin && (c.comment.deleted || c.comment.removed) {
c.comment.content = String::new();
}
c
}
#[cfg(test)]
#[expect(clippy::indexing_slicing)]
mod tests {
@@ -380,7 +358,7 @@ mod tests {
},
instance::Instance,
language::Language,
local_user::{LocalUser, LocalUserInsertForm},
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
local_user_vote_display_mode::LocalUserVoteDisplayMode,
person::{Person, PersonInsertForm},
person_block::{PersonBlock, PersonBlockForm},
@@ -565,6 +543,7 @@ mod tests {
let mut expected_comment_view_with_person = expected_comment_view_no_person.clone();
expected_comment_view_with_person.my_vote = Some(1);
expected_comment_view_with_person.can_mod = true;
let read_comment_views_no_person = CommentQuery {
sort: (Some(CommentSortType::Old)),
@@ -904,6 +883,7 @@ mod tests {
subscribed: SubscribedType::NotSubscribed,
saved: None,
creator_blocked: false,
can_mod: false,
comment: Comment {
id: data.inserted_comment_0.id,
content: "Comment 0".into(),
@@ -1253,6 +1233,16 @@ mod tests {
Comment::update(pool, data.inserted_comment_0.id, &form).await?;
// Read as normal user, content is cleared
// Timmy leaves admin
LocalUser::update(
pool,
data.timmy_local_user_view.local_user.id,
&LocalUserUpdateForm {
admin: Some(false),
..Default::default()
},
)
.await?;
data.timmy_local_user_view.local_user.admin = false;
let comment_view = CommentView::read(
pool,
@@ -1272,6 +1262,15 @@ mod tests {
assert_eq!("", comment_listing[0].comment.content);
// Read as admin, content is returned
LocalUser::update(
pool,
data.timmy_local_user_view.local_user.id,
&LocalUserUpdateForm {
admin: Some(true),
..Default::default()
},
)
.await?;
data.timmy_local_user_view.local_user.admin = true;
let comment_view = CommentView::read(
pool,

View File

@@ -12,10 +12,11 @@ use diesel::{
};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
impls::community::community_follower_select_subscribed_type,
newtypes::{CommunityId, DbUrl, InstanceId, PersonId},
schema::{community, community_actions, person},
source::{
community::{Community, CommunityFollower, CommunityFollowerState},
community::{Community, CommunityFollowerState},
person::Person,
},
utils::{get_conn, limit_and_offset, DbPool},
@@ -150,7 +151,7 @@ impl CommunityFollowerView {
person::all_columns,
community::all_columns,
is_new_instance,
CommunityFollower::select_subscribed_type(),
community_follower_select_subscribed_type(),
))
.into_boxed();
if all_communities {

View File

@@ -12,7 +12,7 @@ use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
impls::local_user::LocalUserOptionHelper,
newtypes::{CommunityId, PersonId},
schema::{community, community_actions, community_aggregates, instance_actions},
schema::{community, community_actions, community_aggregates, instance_actions, local_user},
source::{
community::{Community, CommunityFollowerState},
local_user::LocalUser,
@@ -38,10 +38,13 @@ impl CommunityView {
.and(instance_actions::person_id.nullable().eq(person_id)),
);
let local_user_join = local_user::table.on(local_user::person_id.nullable().eq(person_id));
community::table
.inner_join(community_aggregates::table)
.left_join(community_actions_join)
.left_join(instance_actions_join)
.left_join(local_user_join)
}
pub async fn read(
@@ -209,6 +212,8 @@ mod tests {
CommunityFollowerForm,
CommunityFollowerState,
CommunityInsertForm,
CommunityModerator,
CommunityModeratorForm,
CommunityUpdateForm,
},
instance::Instance,
@@ -216,7 +221,7 @@ mod tests {
person::{Person, PersonInsertForm},
site::Site,
},
traits::{Crud, Followable},
traits::{Crud, Followable, Joinable},
utils::{build_db_pool_for_tests, DbPool},
CommunityVisibility,
SubscribedType,
@@ -446,4 +451,56 @@ mod tests {
cleanup(data, pool).await
}
#[tokio::test]
#[serial]
async fn can_mod() -> LemmyResult<()> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let data = init_data(pool).await?;
// Make sure can_mod is false for all of them.
CommunityQuery {
local_user: Some(&data.local_user),
..Default::default()
}
.list(&data.site, pool)
.await?
.into_iter()
.for_each(|c| assert!(!c.can_mod));
let person_id = data.local_user.person_id;
// Now join the mod team of test community 1 and 2
let mod_form_1 = CommunityModeratorForm {
community_id: data.communities[0].id,
person_id,
};
CommunityModerator::join(pool, &mod_form_1).await?;
let mod_form_2 = CommunityModeratorForm {
community_id: data.communities[1].id,
person_id,
};
CommunityModerator::join(pool, &mod_form_2).await?;
let mod_query = CommunityQuery {
local_user: Some(&data.local_user),
..Default::default()
}
.list(&data.site, pool)
.await?
.into_iter()
.map(|c| (c.community.name, c.can_mod))
.collect::<Vec<_>>();
let expected_communities = vec![
("test_community_1".to_owned(), true),
("test_community_2".to_owned(), true),
("test_community_3".to_owned(), false),
];
assert_eq!(expected_communities, mod_query);
cleanup(data, pool).await
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ use diesel::{
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
aliases::{self, creator_community_actions},
impls::community::community_follower_select_subscribed_type,
newtypes::{CommentReportId, PersonId},
schema::{
comment,
@@ -24,7 +25,6 @@ use lemmy_db_schema::{
person_actions,
post,
},
source::community::CommunityFollower,
utils::{functions::coalesce, get_conn, DbPool},
};
@@ -135,7 +135,7 @@ impl CommentReportView {
.is_not_null(),
local_user::admin.nullable().is_not_null(),
person_actions::blocked.nullable().is_not_null(),
CommunityFollower::select_subscribed_type(),
community_follower_select_subscribed_type(),
comment_actions::saved.nullable(),
comment_actions::like_score.nullable(),
aliases::person2.fields(person::all_columns).nullable(),

View File

@@ -10,6 +10,7 @@ use diesel::{
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
aliases::{self, creator_community_actions},
impls::community::community_follower_select_subscribed_type,
newtypes::{PersonId, PostReportId},
schema::{
community,
@@ -22,7 +23,6 @@ use lemmy_db_schema::{
post_aggregates,
post_report,
},
source::community::CommunityFollower,
utils::{functions::coalesce, get_conn, DbPool},
};
@@ -119,7 +119,7 @@ impl PostReportView {
.nullable()
.is_not_null(),
local_user::admin.nullable().is_not_null(),
CommunityFollower::select_subscribed_type(),
community_follower_select_subscribed_type(),
post_actions::saved.nullable(),
post_actions::read.nullable().is_not_null(),
post_actions::hidden.nullable().is_not_null(),

View File

@@ -3,12 +3,14 @@ use chrono::{DateTime, Utc};
use diesel::{
deserialize::FromSqlRow,
dsl::exists,
dsl::Nullable,
expression::AsExpression,
sql_types,
BoolExpressionMethods,
ExpressionMethods,
NullableExpressionMethods,
PgExpressionMethods,
QueryDsl,
Queryable,
Selectable,
@@ -73,9 +75,11 @@ use lemmy_db_schema::{
};
#[cfg(feature = "full")]
use lemmy_db_schema::{
aliases::{creator_community_actions, person1},
aliases::{creator_community_actions, creator_local_user, person1},
impls::comment::comment_select_remove_deletes,
impls::community::community_follower_select_subscribed_type,
impls::local_user::local_user_can_mod,
schema::{comment, comment_actions, community_actions, local_user, person, person_actions},
source::community::CommunityFollower,
utils::functions::coalesce,
Person1AliasAllColumnsTuple,
};
@@ -119,7 +123,11 @@ pub struct CommentReportView {
#[cfg_attr(feature = "full", ts(export))]
/// A comment view.
pub struct CommentView {
#[cfg_attr(feature = "full", diesel(embed))]
#[cfg_attr(feature = "full",
diesel(
select_expression = comment_select_remove_deletes()
)
)]
pub comment: Comment,
#[cfg_attr(feature = "full", diesel(embed))]
pub creator: Person,
@@ -159,21 +167,17 @@ pub struct CommentView {
#[cfg_attr(feature = "full",
diesel(
select_expression =
exists(
local_user::table.filter(
comment::creator_id
.eq(local_user::person_id)
.and(local_user::admin.eq(true))
)
)
exists(creator_local_user.filter(
comment::creator_id
.eq(creator_local_user.field(local_user::person_id))
.and(creator_local_user.field(local_user::admin).eq(true)),
))
)
)]
pub creator_is_admin: bool,
#[cfg_attr(feature = "full",
diesel(
select_expression_type = Nullable<community_actions::follow_state>,
select_expression =
CommunityFollower::select_subscribed_type(),
select_expression = community_follower_select_subscribed_type(),
)
)]
pub subscribed: SubscribedType,
@@ -201,6 +205,12 @@ pub struct CommentView {
)
)]
pub my_vote: Option<i16>,
#[cfg_attr(feature = "full",
diesel(
select_expression = local_user_can_mod()
)
)]
pub can_mod: bool,
}
#[skip_serializing_none]
@@ -224,6 +234,7 @@ pub struct CommentSlimView {
pub creator_blocked: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub my_vote: Option<i16>,
pub can_mod: bool,
}
#[skip_serializing_none]
@@ -343,6 +354,7 @@ pub struct PostView {
pub my_vote: Option<i16>,
pub unread_comments: i64,
pub tags: PostTags,
pub can_mod: bool,
}
#[skip_serializing_none]
@@ -513,6 +525,7 @@ pub(crate) struct PersonContentCombinedViewInternal {
pub item_creator_banned_from_community: bool,
pub item_creator_blocked: bool,
pub banned_from_community: bool,
pub can_mod: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@@ -570,8 +583,7 @@ pub struct CommunityView {
pub community: Community,
#[cfg_attr(feature = "full",
diesel(
select_expression_type = Nullable<community_actions::follow_state>,
select_expression = CommunityFollower::select_subscribed_type()
select_expression = community_follower_select_subscribed_type()
)
)]
pub subscribed: SubscribedType,
@@ -589,6 +601,14 @@ pub struct CommunityView {
)
)]
pub banned_from_community: bool,
#[cfg_attr(feature = "full",
diesel(
select_expression = local_user::admin.nullable()
.or(community_actions::became_moderator.nullable().is_not_null())
.is_not_distinct_from(true)
)
)]
pub can_mod: bool,
}
/// The community sort types. See here for descriptions: https://join-lemmy.org/docs/en/users/03-votes-and-ranking.html
@@ -637,6 +657,7 @@ pub struct PersonCommentMentionView {
pub creator_blocked: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub my_vote: Option<i16>,
pub can_mod: bool,
}
#[skip_serializing_none]
@@ -669,6 +690,7 @@ pub struct PersonPostMentionView {
pub my_vote: Option<i16>,
pub unread_comments: i64,
pub post_tags: PostTags,
pub can_mod: bool,
}
#[skip_serializing_none]
@@ -696,6 +718,7 @@ pub struct CommentReplyView {
pub creator_blocked: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub my_vote: Option<i16>,
pub can_mod: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@@ -789,6 +812,7 @@ pub struct InboxCombinedViewInternal {
pub item_creator_banned_from_community: bool,
pub item_creator_blocked: bool,
pub banned_from_community: bool,
pub can_mod: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@@ -1167,6 +1191,7 @@ pub(crate) struct SearchCombinedViewInternal {
pub item_creator_banned_from_community: bool,
pub item_creator_blocked: bool,
pub banned_from_community: bool,
pub can_mod: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]