diff --git a/api_tests/package.json b/api_tests/package.json index 8deccd827..660183269 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -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", diff --git a/api_tests/pnpm-lock.yaml b/api_tests/pnpm-lock.yaml index efd592d47..4d86eba46 100644 --- a/api_tests/pnpm-lock.yaml +++ b/api_tests/pnpm-lock.yaml @@ -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: {} diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts index 016574eaa..6ddd0727c 100644 --- a/api_tests/src/community.spec.ts +++ b/api_tests/src/community.spec.ts @@ -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); diff --git a/crates/api/src/community/hide.rs b/crates/api/src/community/hide.rs deleted file mode 100644 index 446ac8c96..000000000 --- a/crates/api/src/community/hide.rs +++ /dev/null @@ -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, - context: Data, - local_user_view: LocalUserView, -) -> LemmyResult> { - // 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())) -} diff --git a/crates/api/src/community/mod.rs b/crates/api/src/community/mod.rs index 121e181c6..f6a248179 100644 --- a/crates/api/src/community/mod.rs +++ b/crates/api/src/community/mod.rs @@ -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; diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index ef909aac8..76ea0c22d 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -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> { 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::(post.ap_id.clone().into(), url.clone().into())?; webmention.set_checked(true); diff --git a/crates/api_crud/src/community/mod.rs b/crates/api_crud/src/community/mod.rs index 0c9a507f1..566264853 100644 --- a/crates/api_crud/src/community/mod.rs +++ b/crates/api_crud/src/community/mod.rs @@ -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, 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(()) diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index d78ead935..2e2f53c6c 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -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 { diff --git a/crates/apub/src/activities/community/mod.rs b/crates/apub/src/activities/community/mod.rs index 1efabe8e2..62bb207d0 100644 --- a/crates/apub/src/activities/community/mod.rs +++ b/crates/apub/src/activities/community/mod.rs @@ -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, ) -> LemmyResult<()> { // If community is local only, don't send anything out - if community.visibility == CommunityVisibility::LocalOnly { + if !community.visibility.can_federate() { return Ok(()); } diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index 50bdcac61..6ddf03186 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -89,6 +89,7 @@ impl ActivityHandler for Follow { } async fn receive(self, context: &Data) -> 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, diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index ea08ac956..28470cde3 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -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(()), } } diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 7121b453e..d5443a711 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -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; diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index 13dc02696..9135b4769 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -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::(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()), } } diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 0376ccfaa..84bae1e11 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -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 }); diff --git a/crates/apub/src/protocol/objects/group.rs b/crates/apub/src/protocol/objects/group.rs index cd0a6f146..5123d4a75 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -77,6 +77,8 @@ pub struct Group { pub(crate) manually_approves_followers: Option, pub(crate) published: Option>, pub(crate) updated: Option>, + /// https://docs.joinmastodon.org/spec/activitypub/#discoverable + pub(crate) discoverable: Option, } impl Group { diff --git a/crates/db_schema/replaceable_schema/triggers.sql b/crates/db_schema/replaceable_schema/triggers.sql index a8fba2cbe..84ee9a48e 100644 --- a/crates/db_schema/replaceable_schema/triggers.sql +++ b/crates/db_schema/replaceable_schema/triggers.sql @@ -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'); diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 711dd08cb..f6098fccc 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -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, diff --git a/crates/db_schema/src/impls/mod_log/moderator.rs b/crates/db_schema/src/impls/mod_log/moderator.rs index 233908c80..c98ccabdb 100644 --- a/crates/db_schema/src/impls/mod_log/moderator.rs +++ b/crates/db_schema/src/impls/mod_log/moderator.rs @@ -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 { let conn = &mut get_conn(pool).await?; - insert_into(mod_hide_community::table) + insert_into(mod_change_community_visibility::table) .values(form) .get_result::(conn) .await @@ -282,7 +282,7 @@ impl Crud for ModHideCommunity { form: &Self::UpdateForm, ) -> Result { 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::(conn) .await diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index e82883b17..e3eda379d 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -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, )] diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index 7e498d9ce..17a49c8e7 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -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))] diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 49693c99c..fbc62c061 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -204,7 +204,6 @@ diesel::table! { followers_url -> Nullable, #[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, + 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, + 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, - hidden -> Bool, - } -} - diesel::table! { mod_lock_post (id) { id -> Int4, @@ -678,12 +680,12 @@ diesel::table! { mod_ban_id -> Nullable, mod_ban_from_community_id -> Nullable, mod_feature_post_id -> Nullable, - mod_hide_community_id -> Nullable, mod_lock_post_id -> Nullable, mod_remove_comment_id -> Nullable, mod_remove_community_id -> Nullable, mod_remove_post_id -> Nullable, mod_transfer_community_id -> Nullable, + mod_change_community_visibility_id -> Nullable, } } @@ -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, diff --git a/crates/db_schema/src/source/combined/modlog.rs b/crates/db_schema/src/source/combined/modlog.rs index a5e488042..ff7da4ab9 100644 --- a/crates/db_schema/src/source/combined/modlog.rs +++ b/crates/db_schema/src/source/combined/modlog.rs @@ -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, pub mod_ban_from_community_id: Option, pub mod_feature_post_id: Option, - pub mod_hide_community_id: Option, + pub mod_change_community_visibility_id: Option, pub mod_lock_post_id: Option, pub mod_remove_comment_id: Option, pub mod_remove_community_id: Option, diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index 33f62edce..184a55d26 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -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, #[new(default)] - pub hidden: Option, - #[new(default)] pub posting_restricted_to_mods: Option, #[new(default)] pub visibility: Option, @@ -171,7 +167,6 @@ pub struct CommunityUpdateForm { pub inbox_url: Option, pub moderators_url: Option, pub featured_url: Option, - pub hidden: Option, pub posting_restricted_to_mods: Option, pub visibility: Option, pub description: Option>, diff --git a/crates/db_schema/src/source/local_site.rs b/crates/db_schema/src/source/local_site.rs index 0641c12f8..870a1536e 100644 --- a/crates/db_schema/src/source/local_site.rs +++ b/crates/db_schema/src/source/local_site.rs @@ -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, + /// 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)] diff --git a/crates/db_schema/src/source/mod_log/moderator.rs b/crates/db_schema/src/source/mod_log/moderator.rs index 2bb536cf9..9466d51a4 100644 --- a/crates/db_schema/src/source/mod_log/moderator.rs +++ b/crates/db_schema/src/source/mod_log/moderator.rs @@ -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, pub reason: Option, + 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, #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, - pub hidden: bool, + pub visibility: CommunityVisibility, } #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] diff --git a/crates/db_views/src/combined/modlog_combined_view.rs b/crates/db_views/src/combined/modlog_combined_view.rs index c7a3dca82..be3747450 100644 --- a/crates/db_views/src/combined/modlog_combined_view.rs +++ b/crates/db_views/src/combined/modlog_combined_view.rs @@ -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"); diff --git a/crates/db_views/src/combined/search_combined_view.rs b/crates/db_views/src/combined/search_combined_view.rs index 0a0ecf4e4..21d2176d1 100644 --- a/crates/db_views/src/combined/search_combined_view.rs +++ b/crates/db_views/src/combined/search_combined_view.rs @@ -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()); diff --git a/crates/db_views/src/comment/comment_view.rs b/crates/db_views/src/comment/comment_view.rs index 3bdb5a108..befd4c04a 100644 --- a/crates/db_views/src/comment/comment_view.rs +++ b/crates/db_views/src/comment/comment_view.rs @@ -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() }, ) diff --git a/crates/db_views/src/community/community_view.rs b/crates/db_views/src/community/community_view.rs index e7245acd7..4eb12da53 100644 --- a/crates/db_views/src/community/community_view.rs +++ b/crates/db_views/src/community/community_view.rs @@ -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() }, ) diff --git a/crates/db_views/src/post/post_view.rs b/crates/db_views/src/post/post_view.rs index 11ee0790c..8296eb864 100644 --- a/crates/db_views/src/post/post_view.rs +++ b/crates/db_views/src/post/post_view.rs @@ -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() }, ) diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index 3d7478ab9..8e5d897b5 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -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, + pub moderator: Option, pub community: Community, } @@ -1068,7 +1068,7 @@ pub(crate) struct ModlogCombinedViewInternal { #[cfg_attr(feature = "full", diesel(embed))] pub mod_feature_post: Option, #[cfg_attr(feature = "full", diesel(embed))] - pub mod_hide_community: Option, + pub mod_change_community_visibility: Option, #[cfg_attr(feature = "full", diesel(embed))] pub mod_lock_post: Option, #[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), diff --git a/crates/db_views/src/utils.rs b/crates/db_views/src/utils.rs index 71f4e2d36..9f98a3173 100644 --- a/crates/db_views/src/utils.rs +++ b/crates/db_views/src/utils.rs @@ -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>; + +pub(crate) fn filter_is_subscribed() -> IsSubscribedType { + community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted)) +} + +type IsNotHiddenType = NotEq; + +pub(crate) fn filter_not_hidden_or_is_subscribed() -> Or { + let not_hidden = community::visibility.ne(CommunityVisibility::Unlisted); + not_hidden.or(filter_is_subscribed()) +} diff --git a/crates/routes/src/feeds.rs b/crates/routes/src/feeds.rs index f917d7d06..565d809fe 100644 --- a/crates/routes/src/feeds.rs +++ b/crates/routes/src/feeds.rs @@ -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()); } diff --git a/crates/routes/src/webfinger.rs b/crates/routes/src/webfinger.rs index f223d33a6..2a3f0f1eb 100644 --- a/crates/routes/src/webfinger.rs +++ b/crates/routes/src/webfinger.rs @@ -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 { diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index cb12cba43..5cf7ae4c8 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -98,7 +98,6 @@ pub enum LemmyErrorType { CommunityUserAlreadyBanned, CommunityBlockAlreadyExists, CommunityFollowerAlreadyExists, - CouldntUpdateCommunityHiddenStatus, PersonBlockAlreadyExists, UserAlreadyExists, CouldntLikePost, diff --git a/migrations/2025-03-11-124442_community-hidden-visibility/down.sql b/migrations/2025-03-11-124442_community-hidden-visibility/down.sql new file mode 100644 index 000000000..05208eb3c --- /dev/null +++ b/migrations/2025-03-11-124442_community-hidden-visibility/down.sql @@ -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__; + diff --git a/migrations/2025-03-11-124442_community-hidden-visibility/up.sql b/migrations/2025-03-11-124442_community-hidden-visibility/up.sql new file mode 100644 index 000000000..13f206245 --- /dev/null +++ b/migrations/2025-03-11-124442_community-hidden-visibility/up.sql @@ -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)); + diff --git a/src/api_routes_v3.rs b/src/api_routes_v3.rs index 4c574ecf4..4ba284f0e 100644 --- a/src/api_routes_v3.rs +++ b/src/api_routes_v3.rs @@ -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)) diff --git a/src/api_routes_v4.rs b/src/api_routes_v4.rs index 985d3ad2f..e243f88de 100644 --- a/src/api_routes_v4.rs +++ b/src/api_routes_v4.rs @@ -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))