diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index 81d5516c1..def86e657 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -5,7 +5,7 @@ use lemmy_api_common::{ comment::{CommentResponse, CreateCommentLike}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::{check_bot_account, check_community_user_action, check_downvotes_enabled}, + utils::{check_bot_account, check_community_user_action, check_local_vote_mode, VoteItem}, }; use lemmy_db_schema::{ newtypes::LocalUserId, @@ -30,8 +30,7 @@ pub async fn like_comment( let mut recipient_ids = Vec::::new(); - // Don't do a downvote if site has downvotes disabled - check_downvotes_enabled(data.score, &local_site)?; + check_local_vote_mode(data.score, VoteItem::Comment, &local_site)?; check_bot_account(&local_user_view.person)?; let comment_id = data.comment_id; diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index 967b22a30..bfd757655 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -8,8 +8,9 @@ use lemmy_api_common::{ utils::{ check_bot_account, check_community_user_action, - check_downvotes_enabled, + check_local_vote_mode, mark_post_as_read, + VoteItem, }, }; use lemmy_db_schema::{ @@ -32,8 +33,7 @@ pub async fn like_post( ) -> LemmyResult> { let local_site = LocalSite::read(&mut context.pool()).await?; - // Don't do a downvote if site has downvotes disabled - check_downvotes_enabled(data.score, &local_site)?; + check_local_vote_mode(data.score, VoteItem::Post, &local_site)?; check_bot_account(&local_user_view.person)?; // Check for a community ban diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index 7204fb66c..d113e90d5 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -21,6 +21,7 @@ use lemmy_db_schema::{ tagline::Tagline, }, CommentSortType, + FederationMode, ListingType, ModlogActionType, PostListingMode, @@ -170,7 +171,6 @@ pub struct CreateSite { pub description: Option, pub icon: Option, pub banner: Option, - pub enable_downvotes: Option, pub enable_nsfw: Option, pub community_creation_admin_only: Option, pub require_email_verification: Option, @@ -208,8 +208,10 @@ pub struct CreateSite { pub registration_mode: Option, pub oauth_registration: Option, pub content_warning: Option, - pub reject_federated_upvotes: Option, - pub reject_federated_downvotes: Option, + pub post_upvotes: Option, + pub post_downvotes: Option, + pub comment_upvotes: Option, + pub comment_downvotes: Option, } #[skip_serializing_none] @@ -226,8 +228,6 @@ pub struct EditSite { pub icon: Option, /// A url for your site's banner. pub banner: Option, - /// Whether to enable downvotes. - pub enable_downvotes: Option, /// Whether to enable NSFW. pub enable_nsfw: Option, /// Limits community creation to admins only. @@ -300,10 +300,14 @@ pub struct EditSite { pub content_warning: Option, /// Whether or not external auth methods can auto-register users. pub oauth_registration: Option, - /// If enabled, your site rejects federated upvotes. - pub reject_federated_upvotes: Option, - /// If enabled, your site rejects federated downvotes. - pub reject_federated_downvotes: Option, + /// What kind of post upvotes your site allows. + pub post_upvotes: Option, + /// What kind of post downvotes your site allows. + pub post_downvotes: Option, + /// What kind of comment upvotes your site allows. + pub comment_upvotes: Option, + /// What kind of comment downvotes your site allows. + pub comment_downvotes: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index b83b9b582..d28e36807 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -33,6 +33,7 @@ use lemmy_db_schema::{ }, traits::Crud, utils::DbPool, + FederationMode, RegistrationMode, }; use lemmy_db_views::{ @@ -296,10 +297,28 @@ pub async fn check_person_instance_community_block( Ok(()) } +/// A vote item type used to check the vote mode. +pub enum VoteItem { + Post, + Comment, +} + #[tracing::instrument(skip_all)] -pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> LemmyResult<()> { - if score == -1 && !local_site.enable_downvotes { - Err(LemmyErrorType::DownvotesAreDisabled)? +pub fn check_local_vote_mode( + score: i16, + vote_item: VoteItem, + local_site: &LocalSite, +) -> LemmyResult<()> { + let (downvote_setting, upvote_setting) = match vote_item { + VoteItem::Post => (local_site.post_downvotes, local_site.post_upvotes), + VoteItem::Comment => (local_site.comment_downvotes, local_site.comment_upvotes), + }; + + let downvote_fail = score == -1 && downvote_setting == FederationMode::Disable; + let upvote_fail = score == 1 && upvote_setting == FederationMode::Disable; + + if downvote_fail || upvote_fail { + Err(LemmyErrorType::VoteNotAllowed)? } else { Ok(()) } diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 77dde1718..b08c9c47c 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -90,7 +90,6 @@ pub async fn create_site( let local_site_form = LocalSiteUpdateForm { // Set the site setup to true site_setup: Some(true), - enable_downvotes: data.enable_downvotes, registration_mode: data.registration_mode, community_creation_admin_only: data.community_creation_admin_only, require_email_verification: data.require_email_verification, @@ -110,8 +109,10 @@ pub async fn create_site( captcha_enabled: data.captcha_enabled, captcha_difficulty: data.captcha_difficulty.clone(), default_post_listing_mode: data.default_post_listing_mode, - reject_federated_upvotes: data.reject_federated_upvotes, - reject_federated_downvotes: data.reject_federated_downvotes, + post_upvotes: data.post_upvotes, + post_downvotes: data.post_downvotes, + comment_upvotes: data.comment_upvotes, + comment_downvotes: data.comment_downvotes, ..Default::default() }; diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index 02b994aae..b2b24c364 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -99,7 +99,6 @@ pub async fn update_site( .ok(); let local_site_form = LocalSiteUpdateForm { - enable_downvotes: data.enable_downvotes, registration_mode: data.registration_mode, community_creation_admin_only: data.community_creation_admin_only, require_email_verification: data.require_email_verification, @@ -121,8 +120,10 @@ pub async fn update_site( reports_email_admins: data.reports_email_admins, default_post_listing_mode: data.default_post_listing_mode, oauth_registration: data.oauth_registration, - reject_federated_upvotes: data.reject_federated_upvotes, - reject_federated_downvotes: data.reject_federated_downvotes, + post_upvotes: data.post_upvotes, + post_downvotes: data.post_downvotes, + comment_upvotes: data.comment_upvotes, + comment_downvotes: data.comment_downvotes, ..Default::default() }; diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 081cea962..9851f3d88 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -18,7 +18,7 @@ use activitypub_federation::{ traits::{ActivityHandler, Actor}, }; use lemmy_api_common::{context::LemmyContext, utils::check_bot_account}; -use lemmy_db_schema::source::local_site::LocalSite; +use lemmy_db_schema::{source::local_site::LocalSite, FederationMode}; use lemmy_utils::error::{LemmyError, LemmyResult}; use url::Url; @@ -69,21 +69,22 @@ impl ActivityHandler for Vote { check_bot_account(&actor.0)?; // Check for enabled federation votes - let local_site = LocalSite::read(&mut context.pool()).await; - let enable_federated_downvotes = local_site - .as_ref() - .map(|l| l.enable_downvotes && !l.reject_federated_downvotes) - .unwrap_or(true); + let local_site = LocalSite::read(&mut context.pool()) + .await + .unwrap_or_default(); - let enable_federated_upvotes = local_site - .as_ref() - .map(|l| !l.reject_federated_upvotes) - .unwrap_or(true); + let (downvote_setting, upvote_setting) = match object { + PostOrComment::Post(_) => (local_site.post_downvotes, local_site.post_upvotes), + PostOrComment::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes), + }; - let reject_vote_check = (self.kind == VoteType::Dislike && !enable_federated_downvotes) - || (self.kind == VoteType::Like && !enable_federated_upvotes); + // Don't allow dislikes for either disabled, or local only votes + let downvote_fail = self.kind == VoteType::Dislike + && [FederationMode::Disable, FederationMode::Local].contains(&downvote_setting); + let upvote_fail = self.kind == VoteType::Like + && [FederationMode::Disable, FederationMode::Local].contains(&upvote_setting); - if reject_vote_check { + if downvote_fail || upvote_fail { // If this is a rejection, undo the vote match object { PostOrComment::Post(p) => undo_vote_post(actor, &p, context).await, diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 963ac63d7..dbadaaf95 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -251,6 +251,27 @@ pub enum CommunityVisibility { LocalOnly, } +#[derive( + EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash, +)] +#[cfg_attr(feature = "full", derive(DbEnum, TS))] +#[cfg_attr( + feature = "full", + ExistingTypePath = "crate::schema::sql_types::FederationModeEnum" +)] +#[cfg_attr(feature = "full", DbValueStyle = "verbatim")] +#[cfg_attr(feature = "full", ts(export))] +/// The federation mode for an item +pub enum FederationMode { + #[default] + /// Allows all + All, + /// Allows only local + Local, + /// Disables + Disable, +} + /// Wrapper for assert_eq! macro. Checks that vec matches the given length, and prints the /// vec on failure. #[macro_export] diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index a55bcfa6b..5534c4f60 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -13,6 +13,10 @@ pub mod sql_types { #[diesel(postgres_type(name = "community_visibility"))] pub struct CommunityVisibility; + #[derive(diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "federation_mode_enum"))] + pub struct FederationModeEnum; + #[derive(diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "listing_type_enum"))] pub struct ListingTypeEnum; @@ -368,12 +372,12 @@ diesel::table! { use super::sql_types::PostListingModeEnum; use super::sql_types::PostSortTypeEnum; use super::sql_types::CommentSortTypeEnum; + use super::sql_types::FederationModeEnum; local_site (id) { id -> Int4, site_id -> Int4, site_setup -> Bool, - enable_downvotes -> Bool, community_creation_admin_only -> Bool, require_email_verification -> Bool, application_question -> Nullable, @@ -398,8 +402,10 @@ diesel::table! { default_post_sort_type -> PostSortTypeEnum, default_comment_sort_type -> CommentSortTypeEnum, oauth_registration -> Bool, - reject_federated_upvotes -> Bool, - reject_federated_downvotes -> Bool, + post_upvotes -> FederationModeEnum, + post_downvotes -> FederationModeEnum, + comment_upvotes -> FederationModeEnum, + comment_downvotes -> FederationModeEnum, } } @@ -771,7 +777,7 @@ diesel::table! { featured_local -> Bool, url_content_type -> Nullable, alt_text -> Nullable, - scheduled_publish_time -> Nullable + scheduled_publish_time -> Nullable, } } diff --git a/crates/db_schema/src/source/local_site.rs b/crates/db_schema/src/source/local_site.rs index 9008ea7d3..5fa57fe3b 100644 --- a/crates/db_schema/src/source/local_site.rs +++ b/crates/db_schema/src/source/local_site.rs @@ -3,6 +3,7 @@ use crate::schema::local_site; use crate::{ newtypes::{LocalSiteId, SiteId}, CommentSortType, + FederationMode, ListingType, PostListingMode, PostSortType, @@ -27,8 +28,6 @@ pub struct LocalSite { pub site_id: SiteId, /// True if the site is set up. pub site_setup: bool, - /// Whether downvotes are enabled. - pub enable_downvotes: bool, /// Whether only admins can create communities. pub community_creation_admin_only: bool, /// Whether emails are required. @@ -72,10 +71,14 @@ pub struct LocalSite { pub default_comment_sort_type: CommentSortType, /// Whether or not external auth methods can auto-register users. pub oauth_registration: bool, - /// If enabled, your site rejects federated upvotes. - pub reject_federated_upvotes: bool, - /// If enabled, your site rejects federated downvotes. - pub reject_federated_downvotes: bool, + /// What kind of post upvotes your site allows. + pub post_upvotes: FederationMode, + /// What kind of post downvotes your site allows. + pub post_downvotes: FederationMode, + /// What kind of comment upvotes your site allows. + pub comment_upvotes: FederationMode, + /// What kind of comment downvotes your site allows. + pub comment_downvotes: FederationMode, } #[derive(Clone, derive_new::new)] @@ -86,8 +89,6 @@ pub struct LocalSiteInsertForm { #[new(default)] pub site_setup: Option, #[new(default)] - pub enable_downvotes: Option, - #[new(default)] pub community_creation_admin_only: Option, #[new(default)] pub require_email_verification: Option, @@ -130,9 +131,13 @@ pub struct LocalSiteInsertForm { #[new(default)] pub oauth_registration: Option, #[new(default)] - pub reject_federated_upvotes: Option, + pub post_upvotes: Option, #[new(default)] - pub reject_federated_downvotes: Option, + pub post_downvotes: Option, + #[new(default)] + pub comment_upvotes: Option, + #[new(default)] + pub comment_downvotes: Option, } #[derive(Clone, Default)] @@ -140,7 +145,6 @@ pub struct LocalSiteInsertForm { #[cfg_attr(feature = "full", diesel(table_name = local_site))] pub struct LocalSiteUpdateForm { pub site_setup: Option, - pub enable_downvotes: Option, pub community_creation_admin_only: Option, pub require_email_verification: Option, pub application_question: Option>, @@ -163,6 +167,8 @@ pub struct LocalSiteUpdateForm { pub default_post_sort_type: Option, pub default_comment_sort_type: Option, pub oauth_registration: Option, - pub reject_federated_upvotes: Option, - pub reject_federated_downvotes: Option, + pub post_upvotes: Option, + pub post_downvotes: Option, + pub comment_upvotes: Option, + pub comment_downvotes: Option, } diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index e03ff2e23..0efabb32c 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -46,7 +46,7 @@ pub enum LemmyErrorType { PersonIsBlocked, CommunityIsBlocked, InstanceIsBlocked, - DownvotesAreDisabled, + VoteNotAllowed, InstanceIsPrivate, /// Password must be between 10 and 60 characters InvalidPassword, diff --git a/migrations/2024-09-20-134838_add_federation_vote_rejection/down.sql b/migrations/2024-09-20-134838_add_federation_vote_rejection/down.sql index 27ce04d69..a9181ca4a 100644 --- a/migrations/2024-09-20-134838_add_federation_vote_rejection/down.sql +++ b/migrations/2024-09-20-134838_add_federation_vote_rejection/down.sql @@ -1,4 +1,31 @@ +-- Add back the enable_downvotes column ALTER TABLE local_site - DROP COLUMN reject_federated_upvotes, - DROP COLUMN reject_federated_downvotes; + ADD COLUMN enable_downvotes boolean DEFAULT TRUE NOT NULL; + +-- regenerate their values (from post_downvotes alone) +WITH subquery AS ( + SELECT + post_downvotes, + CASE WHEN post_downvotes = 'Disable'::federation_mode_enum THEN + FALSE + ELSE + TRUE + END + FROM + local_site) +UPDATE + local_site +SET + enable_downvotes = subquery.case +FROM + subquery; + +-- Drop the new columns +ALTER TABLE local_site + DROP COLUMN post_upvotes, + DROP COLUMN post_downvotes, + DROP COLUMN comment_upvotes, + DROP COLUMN comment_downvotes; + +DROP TYPE federation_mode_enum; diff --git a/migrations/2024-09-20-134838_add_federation_vote_rejection/up.sql b/migrations/2024-09-20-134838_add_federation_vote_rejection/up.sql index 13bc1f582..a2ee6ad4d 100644 --- a/migrations/2024-09-20-134838_add_federation_vote_rejection/up.sql +++ b/migrations/2024-09-20-134838_add_federation_vote_rejection/up.sql @@ -1,4 +1,39 @@ -ALTER TABLE local_site - ADD COLUMN reject_federated_upvotes boolean DEFAULT FALSE NOT NULL, - ADD COLUMN reject_federated_downvotes boolean DEFAULT FALSE NOT NULL; +-- This removes the simple enable_downvotes setting, in favor of an +-- expanded federation mode type for post/comment up/downvotes. +-- Create the federation mode enum +CREATE TYPE federation_mode_enum AS ENUM ( + 'All', + 'Local', + 'Disable' +); + +-- Add the new columns +ALTER TABLE local_site + ADD COLUMN post_upvotes federation_mode_enum DEFAULT 'All'::federation_mode_enum NOT NULL, + ADD COLUMN post_downvotes federation_mode_enum DEFAULT 'All'::federation_mode_enum NOT NULL, + ADD COLUMN comment_upvotes federation_mode_enum DEFAULT 'All'::federation_mode_enum NOT NULL, + ADD COLUMN comment_downvotes federation_mode_enum DEFAULT 'All'::federation_mode_enum NOT NULL; + +-- Copy over the enable_downvotes into the post and comment downvote settings +WITH subquery AS ( + SELECT + enable_downvotes, + CASE WHEN enable_downvotes = TRUE THEN + 'All'::federation_mode_enum + ELSE + 'Disable'::federation_mode_enum + END + FROM + local_site) +UPDATE + local_site +SET + post_downvotes = subquery.case, + comment_downvotes = subquery.case +FROM + subquery; + +-- Drop the enable_downvotes column +ALTER TABLE local_site + DROP COLUMN enable_downvotes;