1
0
Fork 0
mirror of https://github.com/LemmyNet/lemmy.git synced 2025-03-13 15:02:44 +00:00

Changes to community visibility ()

* Move community.hidden to visibility (fixes )

* fix up migration by dropping index

* also add enum variant `LocalOnlyPublic`, rename `LocalOnly` to `LocalOnlyPrivate`

fixes 

* fix column order in down.sql

* wip

* more wip

* fixes

* migration for modlog

* fix migration

* wip

* db_schema compiling

* make the code compile

* lint

* fix down migration

* fix test

* make hidden status federate

* ts attr

* fix

* fix api test

* update api client

* review

* Extracting filter_not_hidden_or_is_subscribed ()

* Extracting filter_not_hidden_or_is_subscribed

* Cleanup.

* Cleanup 2.

* rename hidden to unlisted

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
Nutomic 2025-03-12 13:45:02 +00:00 committed by GitHub
parent d93bfdd2fa
commit 8ffeeca52d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 583 additions and 293 deletions
api_tests
crates
api/src/community
api_common/src
api_crud/src/community
apub/src
activities
http
objects
protocol/objects
db_schema
db_views/src
routes/src
utils/src
migrations/2025-03-11-124442_community-hidden-visibility
src

View file

@ -29,7 +29,7 @@
"eslint": "^9.20.0",
"eslint-plugin-prettier": "^5.2.3",
"jest": "^29.5.0",
"lemmy-js-client": "0.20.0-remove-aggregate-tables.5",
"lemmy-js-client": "0.20.0-move-community-hidden.3",
"prettier": "^3.5.0",
"ts-jest": "^29.1.0",
"tsoa": "^6.6.0",

View file

@ -33,8 +33,8 @@ importers:
specifier: ^29.5.0
version: 29.7.0(@types/node@22.13.1)
lemmy-js-client:
specifier: 0.20.0-remove-aggregate-tables.5
version: 0.20.0-remove-aggregate-tables.5
specifier: 0.20.0-move-community-hidden.3
version: 0.20.0-move-community-hidden.3
prettier:
specifier: ^3.5.0
version: 3.5.0
@ -1528,8 +1528,8 @@ packages:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
lemmy-js-client@0.20.0-remove-aggregate-tables.5:
resolution: {integrity: sha512-A/p4LLWNiVp7fsQOctbFm/biBAunk0FIl5X79WJ/hRu/UiD1M+tCLGYPGdy308R+zscZsDNqECe1LYBenOFzOA==}
lemmy-js-client@0.20.0-move-community-hidden.3:
resolution: {integrity: sha512-X7bbSrnGGgupr//Qk2M1Z9nvFawNU4T116X+4/j912GO6KbQdWN7+10obbQJuoEYr15oCXwSaK8DLusofLxIig==}
leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
@ -4169,7 +4169,7 @@ snapshots:
kleur@3.0.3: {}
lemmy-js-client@0.20.0-remove-aggregate-tables.5: {}
lemmy-js-client@0.20.0-move-community-hidden.3: {}
leven@3.1.0: {}

View file

@ -521,7 +521,7 @@ test("Content in local-only community doesn't federate", async () => {
let communityRes = (await createCommunity(alpha)).community_view.community;
let form: EditCommunity = {
community_id: communityRes.id,
visibility: "LocalOnly",
visibility: "LocalOnlyPublic",
};
await editCommunity(alpha, form);

View file

@ -1,53 +0,0 @@
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
community::HideCommunity,
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::is_admin,
SuccessResponse,
};
use lemmy_db_schema::{
source::{
community::{Community, CommunityUpdateForm},
mod_log::moderator::{ModHideCommunity, ModHideCommunityForm},
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
pub async fn hide_community(
data: Json<HideCommunity>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<SuccessResponse>> {
// Verify its a admin (only admin can hide or unhide it)
is_admin(&local_user_view)?;
let community_form = CommunityUpdateForm {
hidden: Some(data.hidden),
..Default::default()
};
let mod_hide_community_form = ModHideCommunityForm {
community_id: data.community_id,
mod_person_id: local_user_view.person.id,
reason: data.reason.clone(),
hidden: Some(data.hidden),
};
let community_id = data.community_id;
let community = Community::update(&mut context.pool(), community_id, &community_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunityHiddenStatus)?;
ModHideCommunity::create(&mut context.pool(), &mod_hide_community_form).await?;
ActivityChannel::submit_activity(
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
&context,
)?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -2,7 +2,6 @@ pub mod add_mod;
pub mod ban;
pub mod block;
pub mod follow;
pub mod hide;
pub mod pending_follows;
pub mod random;
pub mod transfer;

View file

@ -44,7 +44,6 @@ use lemmy_db_schema::{
},
traits::{Crud, Likeable},
utils::DbPool,
CommunityVisibility,
FederationMode,
RegistrationMode,
};
@ -1190,7 +1189,7 @@ pub fn read_auth_token(req: &HttpRequest) -> LemmyResult<Option<String>> {
pub fn send_webmention(post: Post, community: Community) {
if let Some(url) = post.url.clone() {
if community.visibility == CommunityVisibility::Public {
if community.visibility.can_view_without_login() {
spawn_try_task(async move {
let mut webmention = Webmention::new::<Url>(post.ap_id.clone().into(), url.clone().into())?;
webmention.set_checked(true);

View file

@ -9,13 +9,15 @@ pub mod list;
pub mod remove;
pub mod update;
/// For now only admins can make communities private, in order to prevent abuse.
/// Need to implement admin approval for new communities to get rid of this.
/// For now only admins can make communities private or hidden, in order to
/// prevent abuse. Need to implement admin approval for new communities to
/// get rid of this.
fn check_community_visibility_allowed(
visibility: Option<CommunityVisibility>,
local_user_view: &LocalUserView,
) -> LemmyResult<()> {
if visibility == Some(lemmy_db_schema::CommunityVisibility::Private) {
use CommunityVisibility::*;
if visibility == Some(Private) || visibility == Some(Unlisted) {
is_admin(local_user_view)?;
}
Ok(())

View file

@ -23,10 +23,7 @@ use activitypub_federation::{
traits::{ActivityHandler, Actor},
};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
source::{activity::ActivitySendTargets, community::CommunityFollower},
CommunityVisibility,
};
use lemmy_db_schema::source::{activity::ActivitySendTargets, community::CommunityFollower};
use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResult};
use serde_json::Value;
use url::Url;
@ -211,7 +208,7 @@ async fn can_accept_activity_in_community(
) -> LemmyResult<()> {
if let Some(community) = community {
// Local only community can't federate
if community.visibility == CommunityVisibility::LocalOnly {
if !community.visibility.can_federate() {
return Err(LemmyErrorType::NotFound.into());
}
if !community.local {

View file

@ -14,7 +14,6 @@ use lemmy_db_schema::{
site::Site,
},
traits::Crud,
CommunityVisibility,
};
use lemmy_db_views::structs::CommunityModeratorView;
use lemmy_utils::error::LemmyResult;
@ -50,7 +49,7 @@ pub(crate) async fn send_activity_in_community(
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
// If community is local only, don't send anything out
if community.visibility == CommunityVisibility::LocalOnly {
if !community.visibility.can_federate() {
return Ok(());
}

View file

@ -89,6 +89,7 @@ impl ActivityHandler for Follow {
}
async fn receive(self, context: &Data<LemmyContext>) -> LemmyResult<()> {
use CommunityVisibility::*;
insert_received_activity(&self.id, context).await?;
let actor = self.actor.dereference(context).await?;
let object = self.object.dereference(context).await?;
@ -111,10 +112,10 @@ impl ActivityHandler for Follow {
}
}
let state = Some(match c.visibility {
CommunityVisibility::Public => CommunityFollowerState::Accepted,
CommunityVisibility::Private => CommunityFollowerState::ApprovalRequired,
Public | Unlisted => CommunityFollowerState::Accepted,
Private => CommunityFollowerState::ApprovalRequired,
// Dont allow following local-only community via federation.
CommunityVisibility::LocalOnly => return Err(LemmyErrorType::NotFound.into()),
LocalOnlyPrivate | LocalOnlyPublic => return Err(LemmyErrorType::NotFound.into()),
});
let form = CommunityFollowerForm {
state,

View file

@ -125,9 +125,8 @@ pub(crate) fn verify_visibility(to: &[Url], cc: &[Url], community: &Community) -
use CommunityVisibility::*;
let object_is_public = [to, cc].iter().any(|set| set.contains(&public()));
match community.visibility {
Public if !object_is_public => Err(FederationError::ObjectIsNotPublic)?,
Public | Unlisted if !object_is_public => Err(FederationError::ObjectIsNotPublic)?,
Private if object_is_public => Err(FederationError::ObjectIsNotPrivate)?,
LocalOnly => Err(LemmyErrorType::NotFound.into()),
_ => Ok(()),
}
}

View file

@ -280,7 +280,7 @@ pub(crate) mod tests {
#[serial]
async fn test_get_deleted_community() -> LemmyResult<()> {
let context = LemmyContext::init_test_context().await;
let (instance, _, path) = init(true, CommunityVisibility::LocalOnly, &context).await?;
let (instance, _, path) = init(true, CommunityVisibility::Public, &context).await?;
let request = TestRequest::default().to_http_request();
// should return tombstone
@ -320,7 +320,7 @@ pub(crate) mod tests {
#[serial]
async fn test_get_local_only_community() -> LemmyResult<()> {
let context = LemmyContext::init_test_context().await;
let (instance, _, path) = init(false, CommunityVisibility::LocalOnly, &context).await?;
let (instance, _, path) = init(false, CommunityVisibility::LocalOnlyPrivate, &context).await?;
let request = TestRequest::default().to_http_request();
let res = get_apub_community_http(path.clone().into(), context.reset_request_count()).await;

View file

@ -122,7 +122,7 @@ pub(crate) async fn get_activity(
/// Ensure that the community is public and not removed/deleted.
fn check_community_fetchable(community: &Community) -> LemmyResult<()> {
check_community_removed_or_deleted(community)?;
if community.visibility == CommunityVisibility::LocalOnly {
if !community.visibility.can_federate() {
return Err(LemmyErrorType::NotFound.into());
}
Ok(())
@ -137,12 +137,7 @@ async fn check_community_content_fetchable(
use CommunityVisibility::*;
check_community_removed_or_deleted(community)?;
match community.visibility {
// content in public community can always be fetched
Public => Ok(()),
// no federation for local only community
LocalOnly => Err(LemmyErrorType::NotFound.into()),
// for private community check http signature of request, if there is any approved follower
// from the fetching instance then fetching is allowed
Public | Unlisted => Ok(()),
Private => {
let signing_actor = signing_actor::<SiteOrCommunityOrUser>(request, None, context).await?;
if community.local {
@ -167,6 +162,7 @@ async fn check_community_content_fetchable(
Err(LemmyErrorType::NotFound.into())
}
}
LocalOnlyPublic | LocalOnlyPrivate => Err(LemmyErrorType::NotFound.into()),
}
}

View file

@ -122,6 +122,7 @@ impl Object for ApubCommunity {
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
attributed_to: Some(generate_moderators_url(&self.ap_id)?.into()),
manually_approves_followers: Some(self.visibility == CommunityVisibility::Private),
discoverable: Some(self.visibility != CommunityVisibility::Unlisted),
};
Ok(group)
}
@ -148,6 +149,8 @@ impl Object for ApubCommunity {
let banner = proxy_image_link_opt_apub(group.image.map(|i| i.url), context).await?;
let visibility = Some(if group.manually_approves_followers.unwrap_or_default() {
CommunityVisibility::Private
} else if !group.discoverable.unwrap_or(true) {
CommunityVisibility::Unlisted
} else {
CommunityVisibility::Public
});

View file

@ -77,6 +77,8 @@ pub struct Group {
pub(crate) manually_approves_followers: Option<bool>,
pub(crate) published: Option<DateTime<Utc>>,
pub(crate) updated: Option<DateTime<Utc>>,
/// https://docs.joinmastodon.org/spec/activitypub/#discoverable
pub(crate) discoverable: Option<bool>,
}
impl Group {

View file

@ -595,7 +595,7 @@ CALL r.create_person_saved_combined_trigger ('comment');
-- mod_ban
-- mod_ban_from_community
-- mod_feature_post
-- mod_hide_community
-- mod_change_community_visibility
-- mod_lock_post
-- mod_remove_comment
-- mod_remove_community
@ -646,7 +646,7 @@ CALL r.create_modlog_combined_trigger ('mod_ban_from_community');
CALL r.create_modlog_combined_trigger ('mod_feature_post');
CALL r.create_modlog_combined_trigger ('mod_hide_community');
CALL r.create_modlog_combined_trigger ('mod_change_community_visibility');
CALL r.create_modlog_combined_trigger ('mod_lock_post');

View file

@ -634,7 +634,6 @@ mod tests {
inbox_url: inserted_community.inbox_url.clone(),
moderators_url: None,
featured_url: None,
hidden: false,
posting_restricted_to_mods: false,
instance_id: inserted_instance.id,
visibility: CommunityVisibility::Public,

View file

@ -4,8 +4,8 @@ use crate::{
ModAddId,
ModBanFromCommunityId,
ModBanId,
ModChangeCommunityVisibilityId,
ModFeaturePostId,
ModHideCommunityId,
ModLockPostId,
ModRemoveCommentId,
ModRemoveCommunityId,
@ -17,8 +17,8 @@ use crate::{
mod_add_community,
mod_ban,
mod_ban_from_community,
mod_change_community_visibility,
mod_feature_post,
mod_hide_community,
mod_lock_post,
mod_remove_comment,
mod_remove_community,
@ -34,10 +34,10 @@ use crate::{
ModBanForm,
ModBanFromCommunity,
ModBanFromCommunityForm,
ModChangeCommunityVisibility,
ModChangeCommunityVisibilityForm,
ModFeaturePost,
ModFeaturePostForm,
ModHideCommunity,
ModHideCommunityForm,
ModLockPost,
ModLockPostForm,
ModRemoveComment,
@ -263,14 +263,14 @@ impl Crud for ModBan {
}
}
impl Crud for ModHideCommunity {
type InsertForm = ModHideCommunityForm;
type UpdateForm = ModHideCommunityForm;
type IdType = ModHideCommunityId;
impl Crud for ModChangeCommunityVisibility {
type InsertForm = ModChangeCommunityVisibilityForm;
type UpdateForm = ModChangeCommunityVisibilityForm;
type IdType = ModChangeCommunityVisibilityId;
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(mod_hide_community::table)
insert_into(mod_change_community_visibility::table)
.values(form)
.get_result::<Self>(conn)
.await
@ -282,7 +282,7 @@ impl Crud for ModHideCommunity {
form: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(mod_hide_community::table.find(from_id))
diesel::update(mod_change_community_visibility::table.find(from_id))
.set(form)
.get_result::<Self>(conn)
.await

View file

@ -205,7 +205,7 @@ pub enum ModlogActionType {
ModTransferCommunity,
ModAdd,
ModBan,
ModHideCommunity,
ModChangeCommunityVisibility,
AdminPurgePerson,
AdminPurgeCommunity,
AdminPurgePost,
@ -277,12 +277,28 @@ pub enum CommunityVisibility {
/// Public community, any local or federated user can interact.
#[default]
Public,
/// Unfederated community, only local users can interact.
LocalOnly,
/// Community is hidden and doesn't appear in community list. Post from the community
/// are not shown in Local and All feeds, except for subscribed users.
Unlisted,
/// Unfederated community, only local users can interact (with or without login).
LocalOnlyPublic,
/// Unfederated community, only logged-in local users can interact.
LocalOnlyPrivate,
/// Users need to be approved by mods before they are able to browse or post.
Private,
}
impl CommunityVisibility {
pub fn can_federate(&self) -> bool {
use CommunityVisibility::*;
self != &LocalOnlyPublic && self != &LocalOnlyPrivate
}
pub fn can_view_without_login(&self) -> bool {
use CommunityVisibility::*;
self == &Public || self == &LocalOnlyPublic
}
}
#[derive(
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash,
)]

View file

@ -295,7 +295,7 @@ pub struct ModBanId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ModHideCommunityId(pub i32);
pub struct ModChangeCommunityVisibilityId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]

View file

@ -204,7 +204,6 @@ diesel::table! {
followers_url -> Nullable<Varchar>,
#[max_length = 255]
inbox_url -> Varchar,
hidden -> Bool,
posting_restricted_to_mods -> Bool,
instance_id -> Int4,
#[max_length = 255]
@ -435,6 +434,7 @@ diesel::table! {
comment_downvotes -> FederationModeEnum,
disable_donation_dialog -> Bool,
default_post_time_range_seconds -> Nullable<Int4>,
disallow_nsfw_content -> Bool,
users -> Int8,
posts -> Int8,
comments -> Int8,
@ -443,7 +443,6 @@ diesel::table! {
users_active_week -> Int8,
users_active_month -> Int8,
users_active_half_year -> Int8,
disallow_nsfw_content -> Bool,
}
}
@ -588,6 +587,20 @@ diesel::table! {
}
}
diesel::table! {
use diesel::sql_types::*;
use super::sql_types::CommunityVisibility;
mod_change_community_visibility (id) {
id -> Int4,
community_id -> Int4,
mod_person_id -> Int4,
published -> Timestamptz,
reason -> Nullable<Text>,
visibility -> CommunityVisibility,
}
}
diesel::table! {
mod_feature_post (id) {
id -> Int4,
@ -599,17 +612,6 @@ diesel::table! {
}
}
diesel::table! {
mod_hide_community (id) {
id -> Int4,
community_id -> Int4,
mod_person_id -> Int4,
published -> Timestamptz,
reason -> Nullable<Text>,
hidden -> Bool,
}
}
diesel::table! {
mod_lock_post (id) {
id -> Int4,
@ -678,12 +680,12 @@ diesel::table! {
mod_ban_id -> Nullable<Int4>,
mod_ban_from_community_id -> Nullable<Int4>,
mod_feature_post_id -> Nullable<Int4>,
mod_hide_community_id -> Nullable<Int4>,
mod_lock_post_id -> Nullable<Int4>,
mod_remove_comment_id -> Nullable<Int4>,
mod_remove_community_id -> Nullable<Int4>,
mod_remove_post_id -> Nullable<Int4>,
mod_transfer_community_id -> Nullable<Int4>,
mod_change_community_visibility_id -> Nullable<Int4>,
}
}
@ -1107,10 +1109,10 @@ diesel::joinable!(local_user_language -> local_user (local_user_id));
diesel::joinable!(login_token -> local_user (user_id));
diesel::joinable!(mod_add_community -> community (community_id));
diesel::joinable!(mod_ban_from_community -> community (community_id));
diesel::joinable!(mod_change_community_visibility -> community (community_id));
diesel::joinable!(mod_change_community_visibility -> person (mod_person_id));
diesel::joinable!(mod_feature_post -> person (mod_person_id));
diesel::joinable!(mod_feature_post -> post (post_id));
diesel::joinable!(mod_hide_community -> community (community_id));
diesel::joinable!(mod_hide_community -> person (mod_person_id));
diesel::joinable!(mod_lock_post -> person (mod_person_id));
diesel::joinable!(mod_lock_post -> post (post_id));
diesel::joinable!(mod_remove_comment -> comment (comment_id));
@ -1130,8 +1132,8 @@ diesel::joinable!(modlog_combined -> mod_add (mod_add_id));
diesel::joinable!(modlog_combined -> mod_add_community (mod_add_community_id));
diesel::joinable!(modlog_combined -> mod_ban (mod_ban_id));
diesel::joinable!(modlog_combined -> mod_ban_from_community (mod_ban_from_community_id));
diesel::joinable!(modlog_combined -> mod_change_community_visibility (mod_change_community_visibility_id));
diesel::joinable!(modlog_combined -> mod_feature_post (mod_feature_post_id));
diesel::joinable!(modlog_combined -> mod_hide_community (mod_hide_community_id));
diesel::joinable!(modlog_combined -> mod_lock_post (mod_lock_post_id));
diesel::joinable!(modlog_combined -> mod_remove_comment (mod_remove_comment_id));
diesel::joinable!(modlog_combined -> mod_remove_community (mod_remove_community_id));
@ -1214,8 +1216,8 @@ diesel::allow_tables_to_appear_in_same_query!(
mod_add_community,
mod_ban,
mod_ban_from_community,
mod_change_community_visibility,
mod_feature_post,
mod_hide_community,
mod_lock_post,
mod_remove_comment,
mod_remove_community,

View file

@ -9,8 +9,8 @@ use crate::newtypes::{
ModAddId,
ModBanFromCommunityId,
ModBanId,
ModChangeCommunityVisibilityId,
ModFeaturePostId,
ModHideCommunityId,
ModLockPostId,
ModRemoveCommentId,
ModRemoveCommunityId,
@ -48,7 +48,7 @@ pub struct ModlogCombined {
pub mod_ban_id: Option<ModBanId>,
pub mod_ban_from_community_id: Option<ModBanFromCommunityId>,
pub mod_feature_post_id: Option<ModFeaturePostId>,
pub mod_hide_community_id: Option<ModHideCommunityId>,
pub mod_change_community_visibility_id: Option<ModChangeCommunityVisibilityId>,
pub mod_lock_post_id: Option<ModLockPostId>,
pub mod_remove_comment_id: Option<ModRemoveCommentId>,
pub mod_remove_community_id: Option<ModRemoveCommunityId>,

View file

@ -61,8 +61,6 @@ pub struct Community {
#[cfg_attr(feature = "full", ts(skip))]
#[serde(skip, default = "placeholder_apub_url")]
pub inbox_url: DbUrl,
/// Whether the community is hidden.
pub hidden: bool,
/// Whether posting is restricted to mods only.
pub posting_restricted_to_mods: bool,
pub instance_id: InstanceId,
@ -140,8 +138,6 @@ pub struct CommunityInsertForm {
#[new(default)]
pub featured_url: Option<DbUrl>,
#[new(default)]
pub hidden: Option<bool>,
#[new(default)]
pub posting_restricted_to_mods: Option<bool>,
#[new(default)]
pub visibility: Option<CommunityVisibility>,
@ -171,7 +167,6 @@ pub struct CommunityUpdateForm {
pub inbox_url: Option<DbUrl>,
pub moderators_url: Option<DbUrl>,
pub featured_url: Option<DbUrl>,
pub hidden: Option<bool>,
pub posting_restricted_to_mods: Option<bool>,
pub visibility: Option<CommunityVisibility>,
pub description: Option<Option<String>>,

View file

@ -89,6 +89,8 @@ pub struct LocalSite {
#[cfg_attr(feature = "full", ts(optional))]
/// A default time range limit to apply to post sorts, in seconds.
pub default_post_time_range_seconds: Option<i32>,
/// Block NSFW content being created
pub disallow_nsfw_content: bool,
pub users: i64,
pub posts: i64,
pub comments: i64,
@ -101,8 +103,6 @@ pub struct LocalSite {
pub users_active_month: i64,
/// The number of users with any activity in the last half year.
pub users_active_half_year: i64,
/// Block NSFW content being created
pub disallow_nsfw_content: bool,
}
#[derive(Clone, derive_new::new)]

View file

@ -1,34 +1,37 @@
use crate::newtypes::{
CommentId,
CommunityId,
ModAddCommunityId,
ModAddId,
ModBanFromCommunityId,
ModBanId,
ModFeaturePostId,
ModHideCommunityId,
ModLockPostId,
ModRemoveCommentId,
ModRemoveCommunityId,
ModRemovePostId,
ModTransferCommunityId,
PersonId,
PostId,
};
#[cfg(feature = "full")]
use crate::schema::{
mod_add,
mod_add_community,
mod_ban,
mod_ban_from_community,
mod_change_community_visibility,
mod_feature_post,
mod_hide_community,
mod_lock_post,
mod_remove_comment,
mod_remove_community,
mod_remove_post,
mod_transfer_community,
};
use crate::{
newtypes::{
CommentId,
CommunityId,
ModAddCommunityId,
ModAddId,
ModBanFromCommunityId,
ModBanId,
ModChangeCommunityVisibilityId,
ModFeaturePostId,
ModLockPostId,
ModRemoveCommentId,
ModRemoveCommunityId,
ModRemovePostId,
ModTransferCommunityId,
PersonId,
PostId,
},
CommunityVisibility,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
@ -210,29 +213,28 @@ pub struct ModBan {
}
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = mod_hide_community))]
pub struct ModHideCommunityForm {
#[cfg_attr(feature = "full", diesel(table_name = mod_change_community_visibility))]
pub struct ModChangeCommunityVisibilityForm {
pub community_id: CommunityId,
pub mod_person_id: PersonId,
pub hidden: Option<bool>,
pub reason: Option<String>,
pub visibility: CommunityVisibility,
}
#[skip_serializing_none]
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
#[cfg_attr(feature = "full", diesel(table_name = mod_hide_community))]
#[cfg_attr(feature = "full", diesel(table_name = mod_change_community_visibility))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// When a community is hidden from public view.
pub struct ModHideCommunity {
pub id: ModHideCommunityId,
pub struct ModChangeCommunityVisibility {
pub id: ModChangeCommunityVisibilityId,
pub community_id: CommunityId,
pub mod_person_id: PersonId,
pub published: DateTime<Utc>,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>,
pub hidden: bool,
pub visibility: CommunityVisibility,
}
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]

View file

@ -1,23 +1,26 @@
use crate::structs::{
AdminAllowInstanceView,
AdminBlockInstanceView,
AdminPurgeCommentView,
AdminPurgeCommunityView,
AdminPurgePersonView,
AdminPurgePostView,
ModAddCommunityView,
ModAddView,
ModBanFromCommunityView,
ModBanView,
ModFeaturePostView,
ModHideCommunityView,
ModLockPostView,
ModRemoveCommentView,
ModRemoveCommunityView,
ModRemovePostView,
ModTransferCommunityView,
ModlogCombinedView,
ModlogCombinedViewInternal,
use crate::{
structs::{
AdminAllowInstanceView,
AdminBlockInstanceView,
AdminPurgeCommentView,
AdminPurgeCommunityView,
AdminPurgePersonView,
AdminPurgePostView,
ModAddCommunityView,
ModAddView,
ModBanFromCommunityView,
ModBanView,
ModChangeCommunityVisibilityView,
ModFeaturePostView,
ModLockPostView,
ModRemoveCommentView,
ModRemoveCommunityView,
ModRemovePostView,
ModTransferCommunityView,
ModlogCombinedView,
ModlogCombinedViewInternal,
},
utils::{filter_is_subscribed, filter_not_hidden_or_is_subscribed},
};
use diesel::{
BoolExpressionMethods,
@ -49,8 +52,8 @@ use lemmy_db_schema::{
mod_add_community,
mod_ban,
mod_ban_from_community,
mod_change_community_visibility,
mod_feature_post,
mod_hide_community,
mod_lock_post,
mod_remove_comment,
mod_remove_community,
@ -103,7 +106,7 @@ impl ModlogCombinedViewInternal {
.or(mod_ban::mod_person_id.eq(person::id))
.or(mod_ban_from_community::mod_person_id.eq(person::id))
.or(mod_feature_post::mod_person_id.eq(person::id))
.or(mod_hide_community::mod_person_id.eq(person::id))
.or(mod_change_community_visibility::mod_person_id.eq(person::id))
.or(mod_lock_post::mod_person_id.eq(person::id))
.or(mod_remove_comment::mod_person_id.eq(person::id))
.or(mod_remove_community::mod_person_id.eq(person::id))
@ -167,7 +170,7 @@ impl ModlogCombinedViewInternal {
.is_not_null()
.and(post::community_id.eq(community::id)),
)
.or(mod_hide_community::community_id.eq(community::id))
.or(mod_change_community_visibility::community_id.eq(community::id))
.or(
mod_lock_post::id
.is_not_null()
@ -211,7 +214,7 @@ impl ModlogCombinedViewInternal {
.left_join(mod_ban::table)
.left_join(mod_ban_from_community::table)
.left_join(mod_feature_post::table)
.left_join(mod_hide_community::table)
.left_join(mod_change_community_visibility::table)
.left_join(mod_lock_post::table)
.left_join(mod_remove_comment::table)
.left_join(mod_remove_community::table)
@ -230,24 +233,25 @@ impl ModlogCombinedViewInternal {
impl PaginationCursorBuilder for ModlogCombinedView {
type CursorData = ModlogCombined;
fn to_cursor(&self) -> PaginationCursor {
use ModlogCombinedView::*;
let (prefix, id) = match &self {
ModlogCombinedView::AdminAllowInstance(v) => ('A', v.admin_allow_instance.id.0),
ModlogCombinedView::AdminBlockInstance(v) => ('B', v.admin_block_instance.id.0),
ModlogCombinedView::AdminPurgeComment(v) => ('C', v.admin_purge_comment.id.0),
ModlogCombinedView::AdminPurgeCommunity(v) => ('D', v.admin_purge_community.id.0),
ModlogCombinedView::AdminPurgePerson(v) => ('E', v.admin_purge_person.id.0),
ModlogCombinedView::AdminPurgePost(v) => ('F', v.admin_purge_post.id.0),
ModlogCombinedView::ModAdd(v) => ('G', v.mod_add.id.0),
ModlogCombinedView::ModAddCommunity(v) => ('H', v.mod_add_community.id.0),
ModlogCombinedView::ModBan(v) => ('I', v.mod_ban.id.0),
ModlogCombinedView::ModBanFromCommunity(v) => ('J', v.mod_ban_from_community.id.0),
ModlogCombinedView::ModFeaturePost(v) => ('K', v.mod_feature_post.id.0),
ModlogCombinedView::ModHideCommunity(v) => ('L', v.mod_hide_community.id.0),
ModlogCombinedView::ModLockPost(v) => ('M', v.mod_lock_post.id.0),
ModlogCombinedView::ModRemoveComment(v) => ('N', v.mod_remove_comment.id.0),
ModlogCombinedView::ModRemoveCommunity(v) => ('O', v.mod_remove_community.id.0),
ModlogCombinedView::ModRemovePost(v) => ('P', v.mod_remove_post.id.0),
ModlogCombinedView::ModTransferCommunity(v) => ('Q', v.mod_transfer_community.id.0),
AdminAllowInstance(v) => ('A', v.admin_allow_instance.id.0),
AdminBlockInstance(v) => ('B', v.admin_block_instance.id.0),
AdminPurgeComment(v) => ('C', v.admin_purge_comment.id.0),
AdminPurgeCommunity(v) => ('D', v.admin_purge_community.id.0),
AdminPurgePerson(v) => ('E', v.admin_purge_person.id.0),
AdminPurgePost(v) => ('F', v.admin_purge_post.id.0),
ModAdd(v) => ('G', v.mod_add.id.0),
ModAddCommunity(v) => ('H', v.mod_add_community.id.0),
ModBan(v) => ('I', v.mod_ban.id.0),
ModBanFromCommunity(v) => ('J', v.mod_ban_from_community.id.0),
ModFeaturePost(v) => ('K', v.mod_feature_post.id.0),
ModChangeCommunityVisibility(v) => ('L', v.mod_change_community_visibility.id.0),
ModLockPost(v) => ('M', v.mod_lock_post.id.0),
ModRemoveComment(v) => ('N', v.mod_remove_comment.id.0),
ModRemoveCommunity(v) => ('O', v.mod_remove_community.id.0),
ModRemovePost(v) => ('P', v.mod_remove_post.id.0),
ModTransferCommunity(v) => ('Q', v.mod_transfer_community.id.0),
};
PaginationCursor::new(prefix, id)
}
@ -275,7 +279,7 @@ impl PaginationCursorBuilder for ModlogCombinedView {
'I' => query.filter(modlog_combined::mod_ban_id.eq(id)),
'J' => query.filter(modlog_combined::mod_ban_from_community_id.eq(id)),
'K' => query.filter(modlog_combined::mod_feature_post_id.eq(id)),
'L' => query.filter(modlog_combined::mod_hide_community_id.eq(id)),
'L' => query.filter(modlog_combined::mod_change_community_visibility_id.eq(id)),
'M' => query.filter(modlog_combined::mod_lock_post_id.eq(id)),
'N' => query.filter(modlog_combined::mod_remove_comment_id.eq(id)),
'O' => query.filter(modlog_combined::mod_remove_community_id.eq(id)),
@ -355,7 +359,9 @@ impl ModlogCombinedQuery<'_> {
}
ModAdd => query.filter(modlog_combined::mod_add_id.is_not_null()),
ModBan => query.filter(modlog_combined::mod_ban_id.is_not_null()),
ModHideCommunity => query.filter(modlog_combined::mod_hide_community_id.is_not_null()),
ModChangeCommunityVisibility => {
query.filter(modlog_combined::mod_change_community_visibility_id.is_not_null())
}
AdminPurgePerson => query.filter(modlog_combined::admin_purge_person_id.is_not_null()),
AdminPurgeCommunity => {
query.filter(modlog_combined::admin_purge_community_id.is_not_null())
@ -367,13 +373,12 @@ impl ModlogCombinedQuery<'_> {
}
}
let is_subscribed = community_actions::followed.is_not_null();
query = match self.listing_type.unwrap_or(ListingType::All) {
ListingType::All => query,
ListingType::Subscribed => query.filter(is_subscribed),
ListingType::Subscribed => query.filter(filter_is_subscribed()),
ListingType::Local => query
.filter(community::local.eq(true))
.filter(community::hidden.eq(false).or(is_subscribed)),
.filter(filter_not_hidden_or_is_subscribed()),
ListingType::ModeratorView => query.filter(community_actions::became_moderator.is_not_null()),
};
@ -507,14 +512,16 @@ impl InternalToCombinedView for ModlogCombinedViewInternal {
community,
post,
}))
} else if let (Some(mod_hide_community), Some(community)) =
(v.mod_hide_community, v.community.clone())
} else if let (Some(mod_change_community_visibility), Some(community)) =
(v.mod_change_community_visibility, v.community.clone())
{
Some(ModlogCombinedView::ModHideCommunity(ModHideCommunityView {
mod_hide_community,
admin: v.moderator,
community,
}))
Some(ModlogCombinedView::ModChangeCommunityVisibility(
ModChangeCommunityVisibilityView {
mod_change_community_visibility,
moderator: v.moderator,
community,
},
))
} else if let (Some(mod_lock_post), Some(other_person), Some(community), Some(post)) = (
v.mod_lock_post,
v.other_person.clone(),
@ -626,10 +633,10 @@ mod tests {
ModBanForm,
ModBanFromCommunity,
ModBanFromCommunityForm,
ModChangeCommunityVisibility,
ModChangeCommunityVisibilityForm,
ModFeaturePost,
ModFeaturePostForm,
ModHideCommunity,
ModHideCommunityForm,
ModLockPost,
ModLockPostForm,
ModRemoveComment,
@ -647,6 +654,7 @@ mod tests {
},
traits::Crud,
utils::{build_db_pool_for_tests, DbPool},
CommunityVisibility,
ModlogActionType,
};
use lemmy_utils::error::LemmyResult;
@ -778,43 +786,49 @@ mod tests {
};
AdminPurgePost::create(pool, &form).await?;
let form = ModHideCommunityForm {
let form = ModChangeCommunityVisibilityForm {
mod_person_id: data.timmy.id,
community_id: data.community.id,
hidden: Some(true),
visibility: CommunityVisibility::Unlisted,
reason: None,
};
ModHideCommunity::create(pool, &form).await?;
ModChangeCommunityVisibility::create(pool, &form).await?;
// A 2nd mod hide community, but to a different community, and with jessica
let form = ModHideCommunityForm {
let form = ModChangeCommunityVisibilityForm {
mod_person_id: data.jessica.id,
community_id: data.community_2.id,
hidden: Some(true),
visibility: CommunityVisibility::Unlisted,
reason: None,
};
ModHideCommunity::create(pool, &form).await?;
ModChangeCommunityVisibility::create(pool, &form).await?;
let modlog = ModlogCombinedQuery::default().list(pool).await?;
assert_eq!(8, modlog.len());
if let ModlogCombinedView::ModHideCommunity(v) = &modlog[0] {
assert_eq!(data.community_2.id, v.mod_hide_community.community_id);
if let ModlogCombinedView::ModChangeCommunityVisibility(v) = &modlog[0] {
assert_eq!(
data.community_2.id,
v.mod_change_community_visibility.community_id
);
assert_eq!(data.community_2.id, v.community.id);
assert_eq!(
data.jessica.id,
v.admin.as_ref().map(|a| a.id).unwrap_or(PersonId(-1))
v.moderator.as_ref().map(|a| a.id).unwrap_or(PersonId(-1))
);
} else {
panic!("wrong type");
}
if let ModlogCombinedView::ModHideCommunity(v) = &modlog[1] {
assert_eq!(data.community.id, v.mod_hide_community.community_id);
if let ModlogCombinedView::ModChangeCommunityVisibility(v) = &modlog[1] {
assert_eq!(
data.community.id,
v.mod_change_community_visibility.community_id
);
assert_eq!(data.community.id, v.community.id);
assert_eq!(
data.timmy.id,
v.admin.as_ref().map(|a| a.id).unwrap_or(PersonId(-1))
v.moderator.as_ref().map(|a| a.id).unwrap_or(PersonId(-1))
);
} else {
panic!("wrong type");
@ -906,7 +920,7 @@ mod tests {
// Filter by type
let modlog_type_filter = ModlogCombinedQuery {
type_: Some(ModlogActionType::ModHideCommunity),
type_: Some(ModlogActionType::ModChangeCommunityVisibility),
..Default::default()
}
.list(pool)
@ -915,23 +929,29 @@ mod tests {
// 2 of these, one is jessicas
assert_eq!(2, modlog_type_filter.len());
if let ModlogCombinedView::ModHideCommunity(v) = &modlog_type_filter[0] {
assert_eq!(data.community_2.id, v.mod_hide_community.community_id);
if let ModlogCombinedView::ModChangeCommunityVisibility(v) = &modlog_type_filter[0] {
assert_eq!(
data.community_2.id,
v.mod_change_community_visibility.community_id
);
assert_eq!(data.community_2.id, v.community.id);
assert_eq!(
data.jessica.id,
v.admin.as_ref().map(|a| a.id).unwrap_or(PersonId(-1))
v.moderator.as_ref().map(|a| a.id).unwrap_or(PersonId(-1))
);
} else {
panic!("wrong type");
}
if let ModlogCombinedView::ModHideCommunity(v) = &modlog_type_filter[1] {
assert_eq!(data.community.id, v.mod_hide_community.community_id);
if let ModlogCombinedView::ModChangeCommunityVisibility(v) = &modlog_type_filter[1] {
assert_eq!(
data.community.id,
v.mod_change_community_visibility.community_id
);
assert_eq!(data.community.id, v.community.id);
assert_eq!(
data.timmy.id,
v.admin.as_ref().map(|a| a.id).unwrap_or(PersonId(-1))
v.moderator.as_ref().map(|a| a.id).unwrap_or(PersonId(-1))
);
} else {
panic!("wrong type");

View file

@ -1,11 +1,14 @@
use crate::structs::{
CommentView,
CommunityView,
LocalUserView,
PersonView,
PostView,
SearchCombinedView,
SearchCombinedViewInternal,
use crate::{
structs::{
CommentView,
CommunityView,
LocalUserView,
PersonView,
PostView,
SearchCombinedView,
SearchCombinedViewInternal,
},
utils::{filter_is_subscribed, filter_not_hidden_or_is_subscribed},
};
use diesel::{
dsl::not,
@ -350,24 +353,19 @@ impl SearchCombinedQuery {
};
// Listing type
let is_subscribed = community_actions::followed.is_not_null();
match self.listing_type.unwrap_or_default() {
ListingType::Subscribed => query = query.filter(is_subscribed),
ListingType::Subscribed => query = query.filter(filter_is_subscribed()),
ListingType::Local => {
query = query.filter(
community::local
.eq(true)
.and(community::hidden.eq(false).or(is_subscribed))
.and(filter_not_hidden_or_is_subscribed())
.or(search_combined::person_id.is_not_null().and(person::local)),
);
}
ListingType::All => {
query = query.filter(
community::hidden
.eq(false)
.or(is_subscribed)
.or(search_combined::person_id.is_not_null()),
)
query = query
.filter(filter_not_hidden_or_is_subscribed().or(search_combined::person_id.is_not_null()))
}
ListingType::ModeratorView => {
query = query.filter(community_actions::became_moderator.is_not_null());

View file

@ -972,7 +972,6 @@ mod tests {
description: None,
updated: None,
banner: None,
hidden: false,
posting_restricted_to_mods: false,
published: data.inserted_community.published,
instance_id: data.inserted_instance.id,
@ -1012,7 +1011,7 @@ mod tests {
pool,
data.inserted_community.id,
&CommunityUpdateForm {
visibility: Some(CommunityVisibility::LocalOnly),
visibility: Some(CommunityVisibility::LocalOnlyPrivate),
..Default::default()
},
)

View file

@ -1,4 +1,7 @@
use crate::structs::{CommunityModeratorView, CommunitySortType, CommunityView, PersonView};
use crate::{
structs::{CommunityModeratorView, CommunitySortType, CommunityView, PersonView},
utils::{filter_is_subscribed, filter_not_hidden_or_is_subscribed},
};
use diesel::{
result::Error,
BoolExpressionMethods,
@ -13,11 +16,7 @@ use lemmy_db_schema::{
impls::local_user::LocalUserOptionHelper,
newtypes::{CommunityId, PersonId},
schema::{community, community_actions, instance_actions, local_user},
source::{
community::{Community, CommunityFollowerState},
local_user::LocalUser,
site::Site,
},
source::{community::Community, local_user::LocalUser, site::Site},
utils::{functions::lower, get_conn, limit_and_offset, now, seconds_to_pg_interval, DbPool},
ListingType,
};
@ -130,22 +129,18 @@ impl CommunityQuery<'_> {
// Hide deleted and removed for non-admins or mods
if !o.is_mod_or_admin {
query = query.filter(Community::hide_removed_and_deleted()).filter(
community::hidden
.eq(false)
.or(community_actions::follow_state.is_not_null()),
);
query = query
.filter(Community::hide_removed_and_deleted())
.filter(filter_not_hidden_or_is_subscribed());
}
let is_subscribed = community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted));
if let Some(listing_type) = o.listing_type {
query = match listing_type {
ListingType::All => query.filter(community::hidden.eq(false).or(is_subscribed)),
ListingType::Subscribed => query.filter(is_subscribed),
ListingType::All => query.filter(filter_not_hidden_or_is_subscribed()),
ListingType::Subscribed => query.filter(filter_is_subscribed()),
ListingType::Local => query
.filter(community::local.eq(true))
.filter(community::hidden.eq(false).or(is_subscribed)),
.filter(filter_not_hidden_or_is_subscribed()),
ListingType::ModeratorView => {
query.filter(community_actions::became_moderator.is_not_null())
}
@ -386,7 +381,7 @@ mod tests {
pool,
data.communities[0].id,
&CommunityUpdateForm {
visibility: Some(CommunityVisibility::LocalOnly),
visibility: Some(CommunityVisibility::LocalOnlyPrivate),
..Default::default()
},
)

View file

@ -1,6 +1,6 @@
use crate::{
structs::{PostPaginationCursor, PostView},
utils::filter_blocked,
utils::{filter_blocked, filter_is_subscribed, filter_not_hidden_or_is_subscribed},
};
use diesel::{
debug_query,
@ -460,15 +460,14 @@ impl<'a> PostQuery<'a> {
query = query.filter(post::creator_id.eq(creator_id));
}
let is_subscribed = community_actions::followed.is_not_null();
match o.listing_type.unwrap_or_default() {
ListingType::Subscribed => query = query.filter(is_subscribed),
ListingType::Subscribed => query = query.filter(filter_is_subscribed()),
ListingType::Local => {
query = query
.filter(community::local.eq(true))
.filter(community::hidden.eq(false).or(is_subscribed));
.filter(filter_not_hidden_or_is_subscribed());
}
ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)),
ListingType::All => query = query.filter(filter_not_hidden_or_is_subscribed()),
ListingType::ModeratorView => {
query = query.filter(community_actions::became_moderator.is_not_null());
}
@ -1564,7 +1563,7 @@ mod tests {
pool,
data.community.id,
&CommunityUpdateForm {
hidden: Some(true),
visibility: Some(CommunityVisibility::Unlisted),
..Default::default()
},
)
@ -2010,7 +2009,6 @@ mod tests {
description: None,
updated: None,
banner: None,
hidden: false,
posting_restricted_to_mods: false,
published: inserted_community.published,
instance_id: data.instance.id,
@ -2056,7 +2054,7 @@ mod tests {
pool,
data.community.id,
&CommunityUpdateForm {
visibility: Some(CommunityVisibility::LocalOnly),
visibility: Some(CommunityVisibility::LocalOnlyPrivate),
..Default::default()
},
)

View file

@ -53,8 +53,8 @@ use lemmy_db_schema::{
ModAddCommunity,
ModBan,
ModBanFromCommunity,
ModChangeCommunityVisibility,
ModFeaturePost,
ModHideCommunity,
ModLockPost,
ModRemoveComment,
ModRemoveCommunity,
@ -867,11 +867,11 @@ pub struct ModBanView {
#[cfg_attr(feature = "full", derive(TS, Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// When a community is hidden from public view.
pub struct ModHideCommunityView {
pub mod_hide_community: ModHideCommunity,
/// When the visibility of a community is changed
pub struct ModChangeCommunityVisibilityView {
pub mod_change_community_visibility: ModChangeCommunityVisibility,
#[cfg_attr(feature = "full", ts(optional))]
pub admin: Option<Person>,
pub moderator: Option<Person>,
pub community: Community,
}
@ -1068,7 +1068,7 @@ pub(crate) struct ModlogCombinedViewInternal {
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_feature_post: Option<ModFeaturePost>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_hide_community: Option<ModHideCommunity>,
pub mod_change_community_visibility: Option<ModChangeCommunityVisibility>,
#[cfg_attr(feature = "full", diesel(embed))]
pub mod_lock_post: Option<ModLockPost>,
#[cfg_attr(feature = "full", diesel(embed))]
@ -1118,7 +1118,7 @@ pub enum ModlogCombinedView {
ModBan(ModBanView),
ModBanFromCommunity(ModBanFromCommunityView),
ModFeaturePost(ModFeaturePostView),
ModHideCommunity(ModHideCommunityView),
ModChangeCommunityVisibility(ModChangeCommunityVisibilityView),
ModLockPost(ModLockPostView),
ModRemoveComment(ModRemoveCommentView),
ModRemoveCommunity(ModRemoveCommunityView),

View file

@ -1,5 +1,13 @@
use diesel::{BoolExpressionMethods, ExpressionMethods};
use lemmy_db_schema::schema::{community_actions, instance_actions, person_actions};
use diesel::{
helper_types::{Eq, NotEq, Or},
BoolExpressionMethods,
ExpressionMethods,
};
use lemmy_db_schema::{
schema::{community, community_actions, instance_actions, person_actions},
source::community::CommunityFollowerState,
CommunityVisibility,
};
/// Hide all content from blocked communities and persons. Content from blocked instances is also
/// hidden, unless the user followed the community explicitly.
@ -11,3 +19,17 @@ pub(crate) fn filter_blocked() -> _ {
.and(community_actions::blocked.is_null())
.and(person_actions::blocked.is_null())
}
type IsSubscribedType =
Eq<lemmy_db_schema::schema::community_actions::follow_state, Option<CommunityFollowerState>>;
pub(crate) fn filter_is_subscribed() -> IsSubscribedType {
community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted))
}
type IsNotHiddenType = NotEq<lemmy_db_schema::schema::community::visibility, CommunityVisibility>;
pub(crate) fn filter_not_hidden_or_is_subscribed() -> Or<IsNotHiddenType, IsSubscribedType> {
let not_hidden = community::visibility.ne(CommunityVisibility::Unlisted);
not_hidden.or(filter_is_subscribed())
}

View file

@ -8,7 +8,6 @@ use lemmy_api_common::{
use lemmy_db_schema::{
source::{community::Community, person::Person},
traits::ApubActor,
CommunityVisibility,
ListingType,
PostSortType,
};
@ -274,7 +273,7 @@ async fn get_feed_community(
let community = Community::read_from_name(&mut context.pool(), community_name, false)
.await?
.ok_or(LemmyErrorType::NotFound)?;
if community.visibility != CommunityVisibility::Public {
if !community.visibility.can_view_without_login() {
return Err(LemmyErrorType::NotFound.into());
}

View file

@ -7,7 +7,6 @@ use lemmy_api_common::{context::LemmyContext, LemmyErrorType};
use lemmy_db_schema::{
source::{community::Community, person::Person},
traits::ApubActor,
CommunityVisibility,
};
use lemmy_utils::{
cache_header::cache_3days,
@ -57,7 +56,7 @@ async fn get_webfinger_response(
.ok()
.flatten()
.and_then(|c| {
if c.visibility == CommunityVisibility::Public {
if c.visibility.can_federate() {
let id: Url = c.ap_id.into();
Some(id)
} else {

View file

@ -98,7 +98,6 @@ pub enum LemmyErrorType {
CommunityUserAlreadyBanned,
CommunityBlockAlreadyExists,
CommunityFollowerAlreadyExists,
CouldntUpdateCommunityHiddenStatus,
PersonBlockAlreadyExists,
UserAlreadyExists,
CouldntLikePost,

View file

@ -0,0 +1,240 @@
-- recreate columns in the original order
ALTER TABLE community
ADD COLUMN hidden bool DEFAULT FALSE NOT NULL,
ADD COLUMN posting_restricted_to_mods_new bool NOT NULL DEFAULT FALSE,
ADD COLUMN instance_id_new int NOT NULL,
ADD COLUMN moderators_url_new varchar(255),
ADD COLUMN featured_url_new varchar(255),
ADD COLUMN visibility_new community_visibility NOT NULL DEFAULT 'Public',
ADD COLUMN description_new varchar(150),
ADD COLUMN random_number_new smallint NOT NULL DEFAULT random_smallint (),
ADD COLUMN subscribers_new bigint NOT NULL DEFAULT 0,
ADD COLUMN posts_new bigint NOT NULL DEFAULT 0,
ADD COLUMN comments_new bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_day_new bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_week_new bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_month_new bigint NOT NULL DEFAULT 0,
ADD COLUMN users_active_half_year_new bigint NOT NULL DEFAULT 0,
ADD COLUMN hot_rank_new double precision NOT NULL DEFAULT 0.0001,
ADD COLUMN subscribers_local_new bigint NOT NULL DEFAULT 0,
ADD COLUMN report_count_new smallint NOT NULL DEFAULT 0,
ADD COLUMN unresolved_report_count_new smallint NOT NULL DEFAULT 0,
ADD COLUMN interactions_month_new bigint NOT NULL DEFAULT 0;
UPDATE
community
SET
(posting_restricted_to_mods_new,
instance_id_new,
moderators_url_new,
featured_url_new,
visibility_new,
description_new,
random_number_new,
subscribers_new,
posts_new,
comments_new,
users_active_day_new,
users_active_week_new,
users_active_month_new,
users_active_half_year_new,
hot_rank_new,
subscribers_local_new,
report_count_new,
unresolved_report_count_new,
interactions_month_new) = (posting_restricted_to_mods,
instance_id,
moderators_url,
featured_url,
visibility,
description,
random_number,
subscribers,
posts,
comments,
users_active_day,
users_active_week,
users_active_month,
users_active_half_year,
hot_rank,
subscribers_local,
report_count,
unresolved_report_count,
interactions_month);
ALTER TABLE community
DROP COLUMN posting_restricted_to_mods,
DROP COLUMN instance_id,
DROP COLUMN moderators_url,
DROP COLUMN featured_url,
DROP COLUMN visibility,
DROP COLUMN description,
DROP COLUMN random_number,
DROP COLUMN subscribers,
DROP COLUMN posts,
DROP COLUMN comments,
DROP COLUMN users_active_day,
DROP COLUMN users_active_week,
DROP COLUMN users_active_month,
DROP COLUMN users_active_half_year,
DROP COLUMN hot_rank,
DROP COLUMN subscribers_local,
DROP COLUMN report_count,
DROP COLUMN unresolved_report_count,
DROP COLUMN interactions_month;
ALTER TABLE community RENAME COLUMN posting_restricted_to_mods_new TO posting_restricted_to_mods;
ALTER TABLE community RENAME COLUMN instance_id_new TO instance_id;
ALTER TABLE community RENAME COLUMN moderators_url_new TO moderators_url;
ALTER TABLE community RENAME COLUMN featured_url_new TO featured_url;
ALTER TABLE community RENAME COLUMN visibility_new TO visibility;
ALTER TABLE community RENAME COLUMN description_new TO description;
ALTER TABLE community RENAME COLUMN random_number_new TO random_number;
ALTER TABLE community RENAME COLUMN subscribers_new TO subscribers;
ALTER TABLE community RENAME COLUMN posts_new TO posts;
ALTER TABLE community RENAME COLUMN comments_new TO comments;
ALTER TABLE community RENAME COLUMN users_active_day_new TO users_active_day;
ALTER TABLE community RENAME COLUMN users_active_week_new TO users_active_week;
ALTER TABLE community RENAME COLUMN users_active_month_new TO users_active_month;
ALTER TABLE community RENAME COLUMN users_active_half_year_new TO users_active_half_year;
ALTER TABLE community RENAME COLUMN hot_rank_new TO hot_rank;
ALTER TABLE community RENAME COLUMN subscribers_local_new TO subscribers_local;
ALTER TABLE community RENAME COLUMN report_count_new TO report_count;
ALTER TABLE community RENAME COLUMN unresolved_report_count_new TO unresolved_report_count;
ALTER TABLE community RENAME COLUMN interactions_month_new TO interactions_month;
ALTER TABLE community
ADD CONSTRAINT community_featured_url_key UNIQUE (featured_url),
ADD CONSTRAINT community_moderators_url_key UNIQUE (moderators_url),
ADD CONSTRAINT community_instance_id_fkey FOREIGN KEY (instance_id) REFERENCES instance (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
-- same changes as up.sql, but the other way round
UPDATE
community
SET
(hidden,
visibility) = (TRUE,
'Public')
WHERE
visibility = 'Unlisted';
ALTER TYPE community_visibility RENAME VALUE 'LocalOnlyPrivate' TO 'LocalOnly';
ALTER TYPE community_visibility RENAME TO community_visibility__;
CREATE TYPE community_visibility AS enum (
'Public',
'LocalOnly',
'Private'
);
ALTER TABLE community
ALTER COLUMN visibility DROP DEFAULT;
ALTER TABLE community
ALTER COLUMN visibility TYPE community_visibility
USING visibility::text::community_visibility;
ALTER TABLE community
ALTER COLUMN visibility SET DEFAULT 'Public';
CREATE INDEX idx_community_random_number ON community (random_number) INCLUDE (local, nsfw)
WHERE
NOT (deleted OR removed OR visibility = 'Private');
CREATE INDEX idx_community_nonzero_hotrank ON community USING btree (published)
WHERE (hot_rank <> (0)::double precision);
CREATE INDEX idx_community_subscribers ON community USING btree (subscribers DESC);
CREATE INDEX idx_community_users_active_month ON community USING btree (users_active_month DESC);
CREATE INDEX idx_community_hot ON public.community USING btree (hot_rank DESC);
REINDEX TABLE community;
-- revert modlog table changes
CREATE TABLE mod_hide_community (
id serial PRIMARY KEY,
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
mod_person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamptz NOT NULL DEFAULT now(),
reason text,
hidden boolean DEFAULT FALSE NOT NULL
);
ALTER TABLE modlog_combined
DROP COLUMN mod_change_community_visibility_id,
ADD COLUMN mod_hide_community_id int REFERENCES mod_hide_community ON UPDATE CASCADE ON DELETE CASCADE,
ADD COLUMN mod_lock_post_id_new int,
ADD COLUMN mod_remove_comment_id_new int,
ADD COLUMN mod_remove_community_id_new int,
ADD COLUMN mod_remove_post_id_new int,
ADD COLUMN mod_transfer_community_id_new int;
UPDATE
modlog_combined
SET
(mod_lock_post_id_new,
mod_remove_comment_id_new,
mod_remove_community_id_new,
mod_remove_post_id_new,
mod_transfer_community_id_new) = (mod_lock_post_id,
mod_remove_comment_id,
mod_remove_community_id,
mod_remove_post_id,
mod_transfer_community_id);
ALTER TABLE modlog_combined
DROP COLUMN mod_lock_post_id,
DROP COLUMN mod_remove_comment_id,
DROP COLUMN mod_remove_community_id,
DROP COLUMN mod_remove_post_id,
DROP COLUMN mod_transfer_community_id;
ALTER TABLE modlog_combined RENAME COLUMN mod_lock_post_id_new TO mod_lock_post_id;
ALTER TABLE modlog_combined RENAME COLUMN mod_remove_comment_id_new TO mod_remove_comment_id;
ALTER TABLE modlog_combined RENAME COLUMN mod_remove_community_id_new TO mod_remove_community_id;
ALTER TABLE modlog_combined RENAME COLUMN mod_remove_post_id_new TO mod_remove_post_id;
ALTER TABLE modlog_combined RENAME COLUMN mod_transfer_community_id_new TO mod_transfer_community_id;
ALTER TABLE modlog_combined
ADD CONSTRAINT modlog_combined_mod_hide_community_id_key UNIQUE (mod_hide_community_id),
ADD CONSTRAINT modlog_combined_mod_lock_post_id_key UNIQUE (mod_lock_post_id),
ADD CONSTRAINT modlog_combined_mod_remove_comment_id_key UNIQUE (mod_remove_comment_id),
ADD CONSTRAINT modlog_combined_mod_remove_community_id_key UNIQUE (mod_remove_community_id),
ADD CONSTRAINT modlog_combined_mod_remove_post_id_key UNIQUE (mod_remove_post_id),
ADD CONSTRAINT modlog_combined_mod_transfer_community_id_key UNIQUE (mod_transfer_community_id),
ADD CONSTRAINT modlog_combined_mod_lock_post_id_fkey FOREIGN KEY (mod_lock_post_id) REFERENCES mod_lock_post (id) ON UPDATE CASCADE ON DELETE CASCADE,
ADD CONSTRAINT modlog_combined_mod_remove_comment_id_fkey FOREIGN KEY (mod_remove_comment_id) REFERENCES mod_remove_comment (id) ON UPDATE CASCADE ON DELETE CASCADE,
ADD CONSTRAINT modlog_combined_mod_remove_community_id_fkey FOREIGN KEY (mod_remove_community_id) REFERENCES mod_remove_community (id) ON UPDATE CASCADE ON DELETE CASCADE,
ADD CONSTRAINT modlog_combined_mod_remove_post_id_fkey FOREIGN KEY (mod_remove_post_id) REFERENCES mod_remove_post (id) ON UPDATE CASCADE ON DELETE CASCADE,
ADD CONSTRAINT modlog_combined_mod_transfer_community_id_fkey FOREIGN KEY (mod_transfer_community_id) REFERENCES mod_transfer_community (id) ON UPDATE CASCADE ON DELETE CASCADE,
ADD CONSTRAINT modlog_combined_check CHECK ((num_nonnulls (admin_allow_instance_id, admin_block_instance_id, admin_purge_comment_id, admin_purge_community_id, admin_purge_person_id, admin_purge_post_id, mod_add_id, mod_add_community_id, mod_ban_id, mod_ban_from_community_id, mod_feature_post_id, mod_hide_community_id, mod_lock_post_id, mod_remove_comment_id, mod_remove_community_id, mod_remove_post_id, mod_transfer_community_id) = 1));
DROP TABLE mod_change_community_visibility;
DROP TYPE community_visibility__;

View file

@ -0,0 +1,67 @@
-- Change community.visibility to allow values:
-- ('Public', 'LocalOnlyPublic', 'LocalOnlyPrivate','Private', 'Hidden')
-- rename old enum and add new one
ALTER TYPE community_visibility RENAME TO community_visibility__;
CREATE TYPE community_visibility AS enum (
'Public',
'LocalOnlyPublic',
'LocalOnly',
'Private',
'Unlisted'
);
-- drop default value and index which reference old enum
ALTER TABLE community
ALTER COLUMN visibility DROP DEFAULT;
DROP INDEX idx_community_random_number;
-- change the column type
ALTER TABLE community
ALTER COLUMN visibility TYPE community_visibility
USING visibility::text::community_visibility;
-- add default and index back in
ALTER TABLE community
ALTER COLUMN visibility SET DEFAULT 'Public';
CREATE INDEX idx_community_random_number ON community (random_number) INCLUDE (local, nsfw)
WHERE
NOT (deleted OR removed OR visibility = 'Private' OR visibility = 'Unlisted');
DROP TYPE community_visibility__ CASCADE;
ALTER TYPE community_visibility RENAME VALUE 'LocalOnly' TO 'LocalOnlyPrivate';
-- write hidden value to visibility column
UPDATE
community
SET
visibility = 'Unlisted'
WHERE
hidden;
-- drop the old hidden column
ALTER TABLE community
DROP COLUMN hidden;
-- change modlog tables
ALTER TABLE modlog_combined
DROP COLUMN mod_hide_community_id;
DROP TABLE mod_hide_community;
CREATE TABLE mod_change_community_visibility (
id serial PRIMARY KEY,
community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
mod_person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamptz NOT NULL DEFAULT now(),
reason text,
visibility community_visibility NOT NULL
);
ALTER TABLE modlog_combined
ADD COLUMN mod_change_community_visibility_id int REFERENCES mod_change_community_visibility (id) ON UPDATE CASCADE ON DELETE CASCADE,
ADD CONSTRAINT modlog_combined_check CHECK ((num_nonnulls (admin_allow_instance_id, admin_block_instance_id, admin_purge_comment_id, admin_purge_community_id, admin_purge_person_id, admin_purge_post_id, mod_add_id, mod_add_community_id, mod_ban_id, mod_ban_from_community_id, mod_feature_post_id, mod_change_community_visibility_id, mod_lock_post_id, mod_remove_comment_id, mod_remove_community_id, mod_remove_post_id, mod_transfer_community_id) = 1));

View file

@ -11,7 +11,6 @@ use lemmy_api::{
ban::ban_from_community,
block::user_block_community,
follow::follow_community,
hide::hide_community,
transfer::transfer_community,
},
local_user::{
@ -184,7 +183,6 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
.wrap(rate_limit.message())
.route("", get().to(get_community))
.route("", put().to(update_community))
.route("/hide", put().to(hide_community))
.route("/list", get().to(list_communities))
.route("/follow", post().to(follow_community))
.route("/block", post().to(user_block_community))

View file

@ -11,7 +11,6 @@ use lemmy_api::{
ban::ban_from_community,
block::user_block_community,
follow::follow_community,
hide::hide_community,
pending_follows::{
approve::post_pending_follows_approve,
count::get_pending_follows_count,
@ -215,7 +214,6 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
.route("", get().to(get_community))
.route("", put().to(update_community))
.route("/random", get().to(get_random_community))
.route("/hide", put().to(hide_community))
.route("/list", get().to(list_communities))
.route("/follow", post().to(follow_community))
.route("/report", post().to(create_community_report))