mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-10 19:11:47 +00:00
Add community setting only_followers_can_vote
This commit is contained in:
parent
5a3a4d9fad
commit
d8aa6b3350
15 changed files with 114 additions and 50 deletions
|
@ -5,16 +5,17 @@ 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_community_user_action, check_vote_permission},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::LocalUserId,
|
||||
source::{
|
||||
comment::{CommentLike, CommentLikeForm},
|
||||
comment_reply::CommentReply,
|
||||
community::Community,
|
||||
local_site::LocalSite,
|
||||
},
|
||||
traits::Likeable,
|
||||
traits::{Crud, Likeable},
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
|
@ -30,13 +31,19 @@ pub async fn like_comment(
|
|||
|
||||
let mut recipient_ids = Vec::<LocalUserId>::new();
|
||||
|
||||
// Don't do a downvote if site has downvotes disabled
|
||||
check_downvotes_enabled(data.score, &local_site)?;
|
||||
check_bot_account(&local_user_view.person)?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||
|
||||
let community = Community::read(&mut context.pool(), orig_comment.post.community_id).await?;
|
||||
check_vote_permission(
|
||||
data.score,
|
||||
&local_site,
|
||||
&local_user_view.person,
|
||||
&community,
|
||||
&context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
orig_comment.community.id,
|
||||
|
|
|
@ -5,12 +5,7 @@ use lemmy_api_common::{
|
|||
context::LemmyContext,
|
||||
post::{CreatePostLike, PostResponse},
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{
|
||||
check_bot_account,
|
||||
check_community_user_action,
|
||||
check_downvotes_enabled,
|
||||
mark_post_as_read,
|
||||
},
|
||||
utils::{check_community_user_action, check_vote_permission, mark_post_as_read},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
|
@ -32,14 +27,21 @@ pub async fn like_post(
|
|||
) -> Result<Json<PostResponse>, LemmyError> {
|
||||
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_bot_account(&local_user_view.person)?;
|
||||
|
||||
// Check for a community ban
|
||||
let post_id = data.post_id;
|
||||
let post = Post::read(&mut context.pool(), post_id).await?;
|
||||
|
||||
// TODO: need to read community both here are for check_community_user_action()
|
||||
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||
check_vote_permission(
|
||||
data.score,
|
||||
&local_site,
|
||||
&local_user_view.person,
|
||||
&community,
|
||||
&context,
|
||||
)
|
||||
.await?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
post.community_id,
|
||||
|
|
|
@ -9,7 +9,7 @@ use lemmy_db_schema::{
|
|||
newtypes::{CommunityId, DbUrl, InstanceId, PersonId, PostId},
|
||||
source::{
|
||||
comment::{Comment, CommentUpdateForm},
|
||||
community::{Community, CommunityModerator, CommunityUpdateForm},
|
||||
community::{Community, CommunityFollower, CommunityModerator, CommunityUpdateForm},
|
||||
community_block::CommunityBlock,
|
||||
email_verification::{EmailVerification, EmailVerificationForm},
|
||||
instance::Instance,
|
||||
|
@ -286,25 +286,6 @@ pub async fn check_person_instance_community_block(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(), LemmyError> {
|
||||
if score == -1 && !local_site.enable_downvotes {
|
||||
Err(LemmyErrorType::DownvotesAreDisabled)?
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Dont allow bots to do certain actions, like voting
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn check_bot_account(person: &Person) -> Result<(), LemmyError> {
|
||||
if person.bot_account {
|
||||
Err(LemmyErrorType::InvalidBotAction)?
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn check_private_instance(
|
||||
local_user_view: &Option<LocalUserView>,
|
||||
|
@ -835,6 +816,36 @@ fn limit_expire_time(expires: DateTime<Utc>) -> LemmyResult<Option<DateTime<Utc>
|
|||
}
|
||||
}
|
||||
|
||||
/// Enforce various voting restrictions:
|
||||
/// - Bots are not allowed to vote
|
||||
/// - Instances can disable downvotes
|
||||
/// - Communities can prevent users who are not followers from voting
|
||||
pub async fn check_vote_permission(
|
||||
score: i16,
|
||||
local_site: &LocalSite,
|
||||
person: &Person,
|
||||
community: &Community,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<()> {
|
||||
// check downvotes enabled
|
||||
if score == -1 && !local_site.enable_downvotes {
|
||||
return Err(LemmyErrorType::DownvotesAreDisabled)?;
|
||||
}
|
||||
|
||||
// prevent bots from voting
|
||||
if person.bot_account {
|
||||
return Err(LemmyErrorType::InvalidBotAction)?;
|
||||
}
|
||||
|
||||
if community.only_followers_can_vote
|
||||
&& !CommunityFollower::is_follower(&mut context.pool(), person.id, community.id).await?
|
||||
{
|
||||
// TODO: lemmynsfw code checks that follow was at least 10 minutes ago
|
||||
Err(LemmyErrorType::DownvotesAreDisabled)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
|
|
@ -11,17 +11,18 @@ use crate::{
|
|||
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::DbUrl,
|
||||
newtypes::{CommunityId, DbUrl},
|
||||
source::{
|
||||
activity::ActivitySendTargets,
|
||||
comment::{CommentLike, CommentLikeForm},
|
||||
community::Community,
|
||||
local_site::LocalSite,
|
||||
person::Person,
|
||||
post::{PostLike, PostLikeForm},
|
||||
post::{Post, PostLike, PostLikeForm},
|
||||
},
|
||||
traits::Likeable,
|
||||
traits::{Crud, Likeable},
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||
|
||||
pub mod undo_vote;
|
||||
pub mod vote;
|
||||
|
@ -67,6 +68,9 @@ async fn vote_comment(
|
|||
score: vote_type.into(),
|
||||
};
|
||||
let person_id = actor.id;
|
||||
// TODO: inefficient
|
||||
let post = Post::read(&mut context.pool(), comment.post_id).await?;
|
||||
check_vote_permission(Some(vote_type), &actor, post.community_id, context).await?;
|
||||
CommentLike::remove(&mut context.pool(), person_id, comment_id).await?;
|
||||
CommentLike::like(&mut context.pool(), &like_form).await?;
|
||||
Ok(())
|
||||
|
@ -85,8 +89,9 @@ async fn vote_post(
|
|||
person_id: actor.id,
|
||||
score: vote_type.into(),
|
||||
};
|
||||
let person_id = actor.id;
|
||||
PostLike::remove(&mut context.pool(), person_id, post_id).await?;
|
||||
|
||||
check_vote_permission(Some(vote_type), &actor, post.community_id, context).await?;
|
||||
PostLike::remove(&mut context.pool(), actor.id, post_id).await?;
|
||||
PostLike::like(&mut context.pool(), &like_form).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -99,6 +104,9 @@ async fn undo_vote_comment(
|
|||
) -> Result<(), LemmyError> {
|
||||
let comment_id = comment.id;
|
||||
let person_id = actor.id;
|
||||
// TODO: inefficient
|
||||
let post = Post::read(&mut context.pool(), comment.post_id).await?;
|
||||
check_vote_permission(None, &actor, post.community_id, context).await?;
|
||||
CommentLike::remove(&mut context.pool(), person_id, comment_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -111,6 +119,21 @@ async fn undo_vote_post(
|
|||
) -> Result<(), LemmyError> {
|
||||
let post_id = post.id;
|
||||
let person_id = actor.id;
|
||||
check_vote_permission(None, &actor, post.community_id, context).await?;
|
||||
PostLike::remove(&mut context.pool(), person_id, post_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn check_vote_permission(
|
||||
vote_type: Option<&VoteType>,
|
||||
person: &Person,
|
||||
community_id: CommunityId,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<()> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
let community = Community::read(&mut context.pool(), community_id).await?;
|
||||
let score = vote_type.map(|v| v.into()).unwrap_or(0);
|
||||
lemmy_api_common::utils::check_vote_permission(score, &local_site, &person, &community, &context)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use activitypub_federation::{
|
|||
traits::{ActivityHandler, Actor},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use lemmy_api_common::{context::LemmyContext, utils::check_bot_account};
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::source::local_site::LocalSite;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use url::Url;
|
||||
|
@ -75,8 +75,6 @@ impl ActivityHandler for Vote {
|
|||
let actor = self.actor.dereference(context).await?;
|
||||
let object = self.object.dereference(context).await?;
|
||||
|
||||
check_bot_account(&actor.0)?;
|
||||
|
||||
match object {
|
||||
PostOrComment::Post(p) => vote_post(&self.kind, actor, &p, context).await,
|
||||
PostOrComment::Comment(c) => vote_comment(&self.kind, actor, &c, context).await,
|
||||
|
|
|
@ -109,6 +109,7 @@ impl Object for ApubCommunity {
|
|||
updated: self.updated,
|
||||
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
|
||||
attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()),
|
||||
only_followers_can_vote: Some(self.only_followers_can_vote),
|
||||
};
|
||||
Ok(group)
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ pub struct Group {
|
|||
pub(crate) language: Vec<LanguageTag>,
|
||||
pub(crate) published: Option<DateTime<Utc>>,
|
||||
pub(crate) updated: Option<DateTime<Utc>>,
|
||||
pub(crate) only_followers_can_vote: Option<bool>,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
|
@ -122,6 +123,7 @@ impl Group {
|
|||
posting_restricted_to_mods: self.posting_restricted_to_mods,
|
||||
instance_id,
|
||||
featured_url: self.featured.map(Into::into),
|
||||
only_followers_can_vote: self.only_followers_can_vote,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +154,7 @@ impl Group {
|
|||
moderators_url: self.attributed_to.map(Into::into),
|
||||
posting_restricted_to_mods: self.posting_restricted_to_mods,
|
||||
featured_url: self.featured.map(Into::into),
|
||||
only_followers_can_vote: self.only_followers_can_vote,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,10 @@ use crate::{
|
|||
use diesel::{
|
||||
deserialize,
|
||||
dsl,
|
||||
dsl::insert_into,
|
||||
dsl::{exists, insert_into},
|
||||
pg::Pg,
|
||||
result::Error,
|
||||
select,
|
||||
sql_types,
|
||||
ExpressionMethods,
|
||||
NullableExpressionMethods,
|
||||
|
@ -235,7 +236,6 @@ impl CommunityFollower {
|
|||
remote_community_id: CommunityId,
|
||||
) -> Result<bool, Error> {
|
||||
use crate::schema::community_follower::dsl::{community_follower, community_id};
|
||||
use diesel::dsl::{exists, select};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
select(exists(
|
||||
community_follower.filter(community_id.eq(remote_community_id)),
|
||||
|
@ -243,6 +243,19 @@ impl CommunityFollower {
|
|||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn is_follower(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> Result<bool, Error> {
|
||||
use crate::schema::community_follower::dsl::community_follower;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
select(exists(community_follower.find((person_id, community_id))))
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Queryable<sql_types::Nullable<sql_types::Bool>, Pg> for SubscribedType {
|
||||
|
|
|
@ -183,6 +183,7 @@ diesel::table! {
|
|||
moderators_url -> Nullable<Varchar>,
|
||||
#[max_length = 255]
|
||||
featured_url -> Nullable<Varchar>,
|
||||
only_followers_can_vote -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ pub struct Community {
|
|||
/// Url where featured posts collection is served over Activitypub
|
||||
#[serde(skip)]
|
||||
pub featured_url: Option<DbUrl>,
|
||||
pub only_followers_can_vote: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, TypedBuilder)]
|
||||
|
@ -99,6 +100,7 @@ pub struct CommunityInsertForm {
|
|||
pub posting_restricted_to_mods: Option<bool>,
|
||||
#[builder(!default)]
|
||||
pub instance_id: InstanceId,
|
||||
pub only_followers_can_vote: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
@ -126,6 +128,7 @@ pub struct CommunityUpdateForm {
|
|||
pub featured_url: Option<DbUrl>,
|
||||
pub hidden: Option<bool>,
|
||||
pub posting_restricted_to_mods: Option<bool>,
|
||||
pub only_followers_can_vote: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 2139975ef383077e4709a4f2cae42922fd63b860
|
||||
Subproject commit 3c217c609aa8826fc725f708221c8b3eb825f41a
|
|
@ -1 +0,0 @@
|
|||
alter table local_site drop column content_warning;
|
|
@ -1 +0,0 @@
|
|||
alter table local_site add column content_warning text;
|
2
migrations/2024-01-22-105746_lemmynsfw-changes/down.sql
Normal file
2
migrations/2024-01-22-105746_lemmynsfw-changes/down.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
alter table local_site drop column content_warning;
|
||||
alter table community drop column only_followers_can_vote;
|
2
migrations/2024-01-22-105746_lemmynsfw-changes/up.sql
Normal file
2
migrations/2024-01-22-105746_lemmynsfw-changes/up.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
alter table local_site add column content_warning text;
|
||||
alter table community add column only_followers_can_vote boolean not null default false;
|
Loading…
Reference in a new issue