diff --git a/config/defaults.hjson b/config/defaults.hjson index f0b9d56df..6107589b4 100644 --- a/config/defaults.hjson +++ b/config/defaults.hjson @@ -35,6 +35,9 @@ database: "string" # Maximum number of active sql connections pool_size: 30 + # Set true to store the time when votes were cast. Requires more storage space but can be + # used to detect vote manipulation. + store_vote_timestamps: false } # Pictrs image server configuration. pictrs: { diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index e93b8513f..0ed2edc2a 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -9,11 +9,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ newtypes::LocalUserId, - source::{ - comment::{CommentLike, CommentLikeForm}, - comment_reply::CommentReply, - local_site::LocalSite, - }, + source::{comment::CommentLike, comment_reply::CommentReply, local_site::LocalSite}, traits::Likeable, }; use lemmy_db_views::structs::{CommentView, LocalUserView}; @@ -65,23 +61,23 @@ pub async fn like_comment( } } - let like_form = CommentLikeForm { - comment_id: data.comment_id, - person_id: local_user_view.person.id, - score: data.score, - }; - // Remove any likes first let person_id = local_user_view.person.id; CommentLike::remove(&mut context.pool(), person_id, comment_id).await?; // Only add the like if the score isnt 0 - let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); + let do_add = data.score != 0 && (data.score == 1 || data.score == -1); if do_add { - CommentLike::like(&mut context.pool(), &like_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; + CommentLike::like( + &mut context.pool(), + person_id, + data.comment_id, + data.score, + context.settings(), + ) + .await + .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; } ActivityChannel::submit_activity( diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index c81d9630a..0aef10034 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -17,7 +17,7 @@ use lemmy_db_schema::{ source::{ community::Community, local_site::LocalSite, - post::{Post, PostLike, PostLikeForm}, + post::{Post, PostLike}, }, traits::{Crud, Likeable}, }; @@ -54,23 +54,23 @@ pub async fn like_post( ) .await?; - let like_form = PostLikeForm { - post_id: data.post_id, - person_id: local_user_view.person.id, - score: data.score, - }; - // Remove any likes first let person_id = local_user_view.person.id; PostLike::remove(&mut context.pool(), person_id, post_id).await?; // Only add the like if the score isnt 0 - let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); + let do_add = data.score != 0 && (data.score == 1 || data.score == -1); if do_add { - PostLike::like(&mut context.pool(), &like_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; + PostLike::like( + &mut context.pool(), + person_id, + post_id, + data.score, + context.settings(), + ) + .await + .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; } mark_post_as_read(person_id, post_id, &mut context.pool()).await?; diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 2f67fa7e7..88fae7140 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -19,7 +19,7 @@ use lemmy_db_schema::{ impls::actor_language::default_post_language, source::{ actor_language::CommunityLanguage, - comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm}, + comment::{Comment, CommentInsertForm, CommentLike}, comment_reply::{CommentReply, CommentReplyUpdateForm}, local_site::LocalSite, person_mention::{PersonMention, PersonMentionUpdateForm}, @@ -130,15 +130,15 @@ pub async fn create_comment( .await?; // You like your own comment by default - let like_form = CommentLikeForm { - comment_id: inserted_comment.id, - person_id: local_user_view.person.id, - score: 1, - }; - - CommentLike::like(&mut context.pool(), &like_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; + CommentLike::like( + &mut context.pool(), + local_user_view.person.id, + inserted_comment.id, + 1, + context.settings(), + ) + .await + .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; ActivityChannel::submit_activity( SendActivityData::CreateComment(inserted_comment.clone()), diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 90c68bdbd..8a8b05b59 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -22,7 +22,7 @@ use lemmy_db_schema::{ actor_language::CommunityLanguage, community::Community, local_site::LocalSite, - post::{Post, PostInsertForm, PostLike, PostLikeForm}, + post::{Post, PostInsertForm, PostLike}, }, traits::{Crud, Likeable}, utils::diesel_url_create, @@ -159,15 +159,15 @@ pub async fn create_post( // They like their own post by default let person_id = local_user_view.person.id; let post_id = inserted_post.id; - let like_form = PostLikeForm { - post_id, + PostLike::like( + &mut context.pool(), person_id, - score: 1, - }; - - PostLike::like(&mut context.pool(), &like_form) - .await - .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; + post_id, + 1, + context.settings(), + ) + .await + .with_lemmy_type(LemmyErrorType::CouldntLikePost)?; mark_post_as_read(person_id, post_id, &mut context.pool()).await?; diff --git a/crates/apub/src/activities/create_or_update/comment.rs b/crates/apub/src/activities/create_or_update/comment.rs index 0a0737151..b2a8272e4 100644 --- a/crates/apub/src/activities/create_or_update/comment.rs +++ b/crates/apub/src/activities/create_or_update/comment.rs @@ -32,7 +32,7 @@ use lemmy_db_schema::{ newtypes::PersonId, source::{ activity::ActivitySendTargets, - comment::{Comment, CommentLike, CommentLikeForm}, + comment::{Comment, CommentLike}, community::Community, person::Person, post::Post, @@ -151,12 +151,14 @@ impl ActivityHandler for CreateOrUpdateNote { let comment = ApubComment::from_json(self.object, context).await?; // author likes their own comment by default - let like_form = CommentLikeForm { - comment_id: comment.id, - person_id: comment.creator_id, - score: 1, - }; - CommentLike::like(&mut context.pool(), &like_form).await?; + CommentLike::like( + &mut context.pool(), + comment.creator_id, + comment.id, + 1, + context.settings(), + ) + .await?; // Calculate initial hot_rank CommentAggregates::update_hot_rank(&mut context.pool(), comment.id).await?; diff --git a/crates/apub/src/activities/create_or_update/post.rs b/crates/apub/src/activities/create_or_update/post.rs index fb53100f6..b18b77f96 100644 --- a/crates/apub/src/activities/create_or_update/post.rs +++ b/crates/apub/src/activities/create_or_update/post.rs @@ -28,7 +28,7 @@ use lemmy_db_schema::{ activity::ActivitySendTargets, community::Community, person::Person, - post::{Post, PostLike, PostLikeForm}, + post::{Post, PostLike}, }, traits::{Crud, Likeable}, }; @@ -118,12 +118,14 @@ impl ActivityHandler for CreateOrUpdatePage { let post = ApubPost::from_json(self.object, context).await?; // author likes their own post by default - let like_form = PostLikeForm { - post_id: post.id, - person_id: post.creator_id, - score: 1, - }; - PostLike::like(&mut context.pool(), &like_form).await?; + PostLike::like( + &mut context.pool(), + post.creator_id, + post.id, + 1, + context.settings(), + ) + .await?; // Calculate initial hot_rank for post PostAggregates::update_ranks(&mut context.pool(), post.id).await?; diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs index 7c39b2246..12fe28e2c 100644 --- a/crates/apub/src/activities/voting/mod.rs +++ b/crates/apub/src/activities/voting/mod.rs @@ -14,10 +14,10 @@ use lemmy_db_schema::{ newtypes::DbUrl, source::{ activity::ActivitySendTargets, - comment::{CommentLike, CommentLikeForm}, + comment::CommentLike, community::Community, person::Person, - post::{PostLike, PostLikeForm}, + post::PostLike, }, traits::Likeable, }; @@ -59,15 +59,15 @@ async fn vote_comment( comment: &ApubComment, context: &Data, ) -> LemmyResult<()> { - let comment_id = comment.id; - let like_form = CommentLikeForm { - comment_id, - person_id: actor.id, - score: vote_type.into(), - }; - let person_id = actor.id; - CommentLike::remove(&mut context.pool(), person_id, comment_id).await?; - CommentLike::like(&mut context.pool(), &like_form).await?; + CommentLike::remove(&mut context.pool(), actor.id, comment.id).await?; + CommentLike::like( + &mut context.pool(), + actor.id, + comment.id, + vote_type.into(), + context.settings(), + ) + .await?; Ok(()) } @@ -78,15 +78,15 @@ async fn vote_post( post: &ApubPost, context: &Data, ) -> LemmyResult<()> { - let post_id = post.id; - let like_form = PostLikeForm { - post_id: post.id, - person_id: actor.id, - score: vote_type.into(), - }; - let person_id = actor.id; - PostLike::remove(&mut context.pool(), person_id, post_id).await?; - PostLike::like(&mut context.pool(), &like_form).await?; + PostLike::remove(&mut context.pool(), actor.id, post.id).await?; + PostLike::like( + &mut context.pool(), + actor.id, + post.id, + vote_type.into(), + context.settings(), + ) + .await?; Ok(()) } diff --git a/crates/db_schema/src/aggregates/comment_aggregates.rs b/crates/db_schema/src/aggregates/comment_aggregates.rs index a97bb565b..01e479d4c 100644 --- a/crates/db_schema/src/aggregates/comment_aggregates.rs +++ b/crates/db_schema/src/aggregates/comment_aggregates.rs @@ -35,7 +35,7 @@ mod tests { use crate::{ aggregates::comment_aggregates::CommentAggregates, source::{ - comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm}, + comment::{Comment, CommentInsertForm, CommentLike}, community::{Community, CommunityInsertForm}, instance::Instance, person::{Person, PersonInsertForm}, @@ -45,6 +45,7 @@ mod tests { utils::build_db_pool_for_tests, }; use diesel::result::Error; + use lemmy_utils::settings::structs::Settings; use pretty_assertions::assert_eq; use serial_test::serial; @@ -94,13 +95,8 @@ mod tests { let _inserted_child_comment = Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; - let comment_like = CommentLikeForm { - comment_id: inserted_comment.id, - person_id: inserted_person.id, - score: 1, - }; - - CommentLike::like(pool, &comment_like).await?; + let settings = Settings::default(); + CommentLike::like(pool, inserted_person.id, inserted_comment.id, 1, &settings).await?; let comment_aggs_before_delete = CommentAggregates::read(pool, inserted_comment.id).await?; @@ -109,13 +105,14 @@ mod tests { assert_eq!(0, comment_aggs_before_delete.downvotes); // Add a post dislike from the other person - let comment_dislike = CommentLikeForm { - comment_id: inserted_comment.id, - person_id: another_inserted_person.id, - score: -1, - }; - - CommentLike::like(pool, &comment_dislike).await?; + CommentLike::like( + pool, + another_inserted_person.id, + inserted_comment.id, + -1, + &settings, + ) + .await?; let comment_aggs_after_dislike = CommentAggregates::read(pool, inserted_comment.id).await?; diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index 6e0eacc07..0a9484613 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -20,16 +20,17 @@ mod tests { use crate::{ aggregates::person_aggregates::PersonAggregates, source::{ - comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm}, + comment::{Comment, CommentInsertForm, CommentLike, CommentUpdateForm}, community::{Community, CommunityInsertForm}, instance::Instance, person::{Person, PersonInsertForm}, - post::{Post, PostInsertForm, PostLike, PostLikeForm}, + post::{Post, PostInsertForm, PostLike}, }, traits::{Crud, Likeable}, utils::build_db_pool_for_tests, }; use diesel::result::Error; + use lemmy_utils::settings::structs::Settings; use pretty_assertions::assert_eq; use serial_test::serial; @@ -65,12 +66,8 @@ mod tests { ); let inserted_post = Post::create(pool, &new_post).await?; - let post_like = PostLikeForm { - post_id: inserted_post.id, - person_id: inserted_person.id, - score: 1, - }; - let _inserted_post_like = PostLike::like(pool, &post_like).await?; + let settings = Settings::default(); + PostLike::like(pool, inserted_person.id, inserted_post.id, 1, &settings).await?; let comment_form = CommentInsertForm::new( inserted_person.id, @@ -79,13 +76,7 @@ mod tests { ); let inserted_comment = Comment::create(pool, &comment_form, None).await?; - let mut comment_like = CommentLikeForm { - comment_id: inserted_comment.id, - person_id: inserted_person.id, - score: 1, - }; - - let _inserted_comment_like = CommentLike::like(pool, &comment_like).await?; + CommentLike::like(pool, inserted_person.id, inserted_comment.id, 1, &settings).await?; let child_comment_form = CommentInsertForm::new( inserted_person.id, @@ -95,13 +86,14 @@ mod tests { let inserted_child_comment = Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; - let child_comment_like = CommentLikeForm { - comment_id: inserted_child_comment.id, - person_id: another_inserted_person.id, - score: 1, - }; - - let _inserted_child_comment_like = CommentLike::like(pool, &child_comment_like).await?; + CommentLike::like( + pool, + another_inserted_person.id, + inserted_child_comment.id, + 1, + &settings, + ) + .await?; let person_aggregates_before_delete = PersonAggregates::read(pool, inserted_person.id).await?; @@ -151,8 +143,14 @@ mod tests { let new_parent_comment = Comment::create(pool, &comment_form, None).await?; let _new_child_comment = Comment::create(pool, &child_comment_form, Some(&new_parent_comment.path)).await?; - comment_like.comment_id = new_parent_comment.id; - CommentLike::like(pool, &comment_like).await?; + CommentLike::like( + pool, + inserted_person.id, + new_parent_comment.id, + 1, + &settings, + ) + .await?; let after_comment_add = PersonAggregates::read(pool, inserted_person.id).await?; assert_eq!(2, after_comment_add.comment_count); // TODO: fix person aggregate comment score calculation diff --git a/crates/db_schema/src/aggregates/post_aggregates.rs b/crates/db_schema/src/aggregates/post_aggregates.rs index b63017317..9b7a041bb 100644 --- a/crates/db_schema/src/aggregates/post_aggregates.rs +++ b/crates/db_schema/src/aggregates/post_aggregates.rs @@ -58,12 +58,13 @@ mod tests { community::{Community, CommunityInsertForm}, instance::Instance, person::{Person, PersonInsertForm}, - post::{Post, PostInsertForm, PostLike, PostLikeForm}, + post::{Post, PostInsertForm, PostLike}, }, traits::{Crud, Likeable}, utils::build_db_pool_for_tests, }; use diesel::result::Error; + use lemmy_utils::settings::structs::Settings; use pretty_assertions::assert_eq; use serial_test::serial; @@ -113,13 +114,8 @@ mod tests { let inserted_child_comment = Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; - let post_like = PostLikeForm { - post_id: inserted_post.id, - person_id: inserted_person.id, - score: 1, - }; - - PostLike::like(pool, &post_like).await?; + let settings = Settings::default(); + PostLike::like(pool, inserted_person.id, inserted_post.id, 1, &settings).await?; let post_aggs_before_delete = PostAggregates::read(pool, inserted_post.id).await?; @@ -129,13 +125,14 @@ mod tests { assert_eq!(0, post_aggs_before_delete.downvotes); // Add a post dislike from the other person - let post_dislike = PostLikeForm { - post_id: inserted_post.id, - person_id: another_inserted_person.id, - score: -1, - }; - - PostLike::like(pool, &post_dislike).await?; + PostLike::like( + pool, + another_inserted_person.id, + inserted_post.id, + -1, + &settings, + ) + .await?; let post_aggs_after_dislike = PostAggregates::read(pool, inserted_post.id).await?; diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index e1d48d578..e39eee66b 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -18,6 +18,7 @@ use chrono::{DateTime, Utc}; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use diesel_ltree::Ltree; +use lemmy_utils::settings::structs::Settings; use url::Url; impl Comment { @@ -138,16 +139,32 @@ impl Crud for Comment { #[async_trait] impl Likeable for CommentLike { - type Form = CommentLikeForm; type IdType = CommentId; - async fn like(pool: &mut DbPool<'_>, comment_like_form: &CommentLikeForm) -> Result { - use crate::schema::comment_like::dsl::{comment_id, comment_like, person_id}; + async fn like( + pool: &mut DbPool<'_>, + person_id: PersonId, + comment_id: Self::IdType, + score: i16, + settings: &Settings, + ) -> Result { + use crate::schema::comment_like; let conn = &mut get_conn(pool).await?; - insert_into(comment_like) - .values(comment_like_form) - .on_conflict((comment_id, person_id)) + let published = if settings.database.store_vote_timestamps { + Some(Utc::now()) + } else { + None + }; + let form = CommentLikeForm { + person_id, + comment_id, + score, + published, + }; + insert_into(comment_like::table) + .values(&form) + .on_conflict((comment_like::comment_id, comment_like::person_id)) .do_update() - .set(comment_like_form) + .set(&form) .get_result::(conn) .await } @@ -205,7 +222,6 @@ mod tests { Comment, CommentInsertForm, CommentLike, - CommentLikeForm, CommentSaved, CommentSavedForm, CommentUpdateForm, @@ -219,7 +235,7 @@ mod tests { utils::build_db_pool_for_tests, }; use diesel_ltree::Ltree; - use lemmy_utils::error::LemmyResult; + use lemmy_utils::{error::LemmyResult, settings::structs::Settings}; use pretty_assertions::assert_eq; use serial_test::serial; use url::Url; @@ -287,18 +303,15 @@ mod tests { Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; // Comment Like - let comment_like_form = CommentLikeForm { - comment_id: inserted_comment.id, - person_id: inserted_person.id, - score: 1, - }; - - let inserted_comment_like = CommentLike::like(pool, &comment_like_form).await?; + let settings = Settings::default(); + let inserted_comment_like = + CommentLike::like(pool, inserted_person.id, inserted_comment.id, 1, &settings).await?; let expected_comment_like = CommentLike { comment_id: inserted_comment.id, person_id: inserted_person.id, score: 1, + published: None, }; // Comment Saved diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 0a0b34db8..4915d387b 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -39,6 +39,7 @@ use diesel::{ TextExpressionMethods, }; use diesel_async::RunQueryDsl; +use lemmy_utils::settings::structs::Settings; use std::collections::HashSet; #[async_trait] @@ -274,15 +275,31 @@ impl Post { #[async_trait] impl Likeable for PostLike { - type Form = PostLikeForm; type IdType = PostId; - async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result { + async fn like( + pool: &mut DbPool<'_>, + person_id: PersonId, + post_id: Self::IdType, + score: i16, + settings: &Settings, + ) -> Result { let conn = &mut get_conn(pool).await?; + let published = if settings.database.store_vote_timestamps { + Some(Utc::now()) + } else { + None + }; + let form = PostLikeForm { + person_id, + post_id, + score, + published, + }; insert_into(post_like::table) - .values(post_like_form) + .values(&form) .on_conflict((post_like::post_id, post_like::person_id)) .do_update() - .set(post_like_form) + .set(&form) .get_result::(conn) .await } @@ -399,22 +416,13 @@ mod tests { community::{Community, CommunityInsertForm}, instance::Instance, person::{Person, PersonInsertForm}, - post::{ - Post, - PostInsertForm, - PostLike, - PostLikeForm, - PostRead, - PostSaved, - PostSavedForm, - PostUpdateForm, - }, + post::{Post, PostInsertForm, PostLike, PostRead, PostSaved, PostSavedForm, PostUpdateForm}, }, traits::{Crud, Likeable, Saveable}, utils::build_db_pool_for_tests, }; use chrono::DateTime; - use lemmy_utils::error::LemmyResult; + use lemmy_utils::{error::LemmyResult, settings::structs::Settings}; use pretty_assertions::assert_eq; use serial_test::serial; use std::collections::HashSet; @@ -489,18 +497,15 @@ mod tests { }; // Post Like - let post_like_form = PostLikeForm { - post_id: inserted_post.id, - person_id: inserted_person.id, - score: 1, - }; - - let inserted_post_like = PostLike::like(pool, &post_like_form).await?; + let settings = Settings::default(); + let inserted_post_like = + PostLike::like(pool, inserted_person.id, inserted_post.id, 1, &settings).await?; let expected_post_like = PostLike { post_id: inserted_post.id, person_id: inserted_person.id, score: 1, + published: None, }; // Post Save diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index af7209edd..5ee7d2c42 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -124,6 +124,7 @@ diesel::table! { person_id -> Int4, comment_id -> Int4, score -> Int2, + published -> Nullable, } } @@ -814,6 +815,7 @@ diesel::table! { post_id -> Int4, person_id -> Int4, score -> Int2, + published -> Nullable, } } diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 51e12c45e..5a736c63f 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -103,15 +103,17 @@ pub struct CommentLike { pub person_id: PersonId, pub comment_id: CommentId, pub score: i16, + pub published: Option>, } #[derive(Clone)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = comment_like))] -pub struct CommentLikeForm { +pub(crate) struct CommentLikeForm { pub person_id: PersonId, pub comment_id: CommentId, pub score: i16, + pub published: Option>, } #[derive(PartialEq, Eq, Debug)] diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index e1de250c6..d418be459 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -150,15 +150,17 @@ pub struct PostLike { pub post_id: PostId, pub person_id: PersonId, pub score: i16, + pub published: Option>, } #[derive(Clone)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = post_like))] -pub struct PostLikeForm { +pub(crate) struct PostLikeForm { pub post_id: PostId, pub person_id: PersonId, pub score: i16, + pub published: Option>, } #[derive(PartialEq, Eq, Debug)] diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index 74f5ea009..3df67480e 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -15,6 +15,7 @@ use diesel_async::{ AsyncPgConnection, RunQueryDsl, }; +use lemmy_utils::settings::structs::Settings; /// Returned by `diesel::delete` pub type Delete = DeleteStatement<::Table, ::WhereClause>; @@ -94,9 +95,14 @@ pub trait Joinable { #[async_trait] pub trait Likeable { - type Form; type IdType; - async fn like(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn like( + pool: &mut DbPool<'_>, + person_id: PersonId, + item_id: Self::IdType, + score: i16, + settings: &Settings, + ) -> Result where Self: Sized; async fn remove( diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 55fea4d66..b0171d244 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -438,7 +438,6 @@ mod tests { Comment, CommentInsertForm, CommentLike, - CommentLikeForm, CommentSaved, CommentSavedForm, CommentUpdateForm, @@ -466,7 +465,7 @@ mod tests { CommunityVisibility, SubscribedType, }; - use lemmy_utils::error::LemmyResult; + use lemmy_utils::{error::LemmyResult, settings::structs::Settings}; use pretty_assertions::assert_eq; use serial_test::serial; @@ -599,13 +598,15 @@ mod tests { }; assert_eq!(expected_block, inserted_block); - let comment_like_form = CommentLikeForm { - comment_id: inserted_comment_0.id, - person_id: inserted_timmy_person.id, - score: 1, - }; - - let _inserted_comment_like = CommentLike::like(pool, &comment_like_form).await?; + let settings = Settings::default(); + CommentLike::like( + pool, + inserted_timmy_person.id, + inserted_comment_0.id, + 1, + &settings, + ) + .await?; let timmy_local_user_view = LocalUserView { local_user: inserted_timmy_local_user.clone(), @@ -698,12 +699,15 @@ mod tests { PersonBlock::unblock(pool, &timmy_unblocks_sara_form).await?; // Like a new comment - let comment_like_form = CommentLikeForm { - comment_id: data.inserted_comment_1.id, - person_id: data.timmy_local_user_view.person.id, - score: 1, - }; - CommentLike::like(pool, &comment_like_form).await?; + let settings = Settings::default(); + CommentLike::like( + pool, + data.timmy_local_user_view.person.id, + data.inserted_comment_1.id, + 1, + &settings, + ) + .await?; let read_liked_comment_views = CommentQuery { local_user: Some(&data.timmy_local_user_view.local_user), diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index d05d668c2..fdcd23c6d 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -759,7 +759,6 @@ mod tests { PostHide, PostInsertForm, PostLike, - PostLikeForm, PostRead, PostSaved, PostSavedForm, @@ -773,7 +772,7 @@ mod tests { PostSortType, SubscribedType, }; - use lemmy_utils::error::LemmyResult; + use lemmy_utils::{error::LemmyResult, settings::structs::Settings}; use pretty_assertions::assert_eq; use serial_test::serial; use std::{collections::HashSet, time::Duration}; @@ -1114,18 +1113,21 @@ mod tests { let pool = &mut pool.into(); let mut data = init_data(pool).await?; - let post_like_form = PostLikeForm { - post_id: data.inserted_post.id, - person_id: data.local_user_view.person.id, - score: 1, - }; - - let inserted_post_like = PostLike::like(pool, &post_like_form).await?; + let settings = Settings::default(); + let inserted_post_like = PostLike::like( + pool, + data.local_user_view.person.id, + data.inserted_post.id, + 1, + &settings, + ) + .await?; let expected_post_like = PostLike { post_id: data.inserted_post.id, person_id: data.local_user_view.person.id, score: 1, + published: None, }; assert_eq!(expected_post_like, inserted_post_like); @@ -1173,19 +1175,24 @@ mod tests { // Like both the bot post, and your own // The liked_only should not show your own post - let post_like_form = PostLikeForm { - post_id: data.inserted_post.id, - person_id: data.local_user_view.person.id, - score: 1, - }; - PostLike::like(pool, &post_like_form).await?; + let settings = Settings::default(); + PostLike::like( + pool, + data.local_user_view.person.id, + data.inserted_post.id, + 1, + &settings, + ) + .await?; - let bot_post_like_form = PostLikeForm { - post_id: data.inserted_bot_post.id, - person_id: data.local_user_view.person.id, - score: 1, - }; - PostLike::like(pool, &bot_post_like_form).await?; + PostLike::like( + pool, + data.local_user_view.person.id, + data.inserted_bot_post.id, + 1, + &settings, + ) + .await?; // Read the liked only let read_liked_post_listing = PostQuery { diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index 3c219d63f..7e295e1a4 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; #[cfg(feature = "full")] use diesel::Queryable; use lemmy_db_schema::{ @@ -216,6 +217,7 @@ pub struct VoteView { pub creator: Person, pub creator_banned_from_community: bool, pub score: i16, + pub published: Option>, } #[skip_serializing_none] diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 0fd64deca..357a2ecda 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -40,6 +40,7 @@ impl VoteView { person::all_columns, community_person_ban::community_id.nullable().is_not_null(), post_like::score, + post_like::published, )) .order_by(post_like::score) .limit(limit) @@ -74,6 +75,7 @@ impl VoteView { person::all_columns, community_person_ban::community_id.nullable().is_not_null(), comment_like::score, + comment_like::published, )) .order_by(comment_like::score) .limit(limit) @@ -89,16 +91,16 @@ mod tests { use crate::structs::VoteView; use lemmy_db_schema::{ source::{ - comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm}, + comment::{Comment, CommentInsertForm, CommentLike}, community::{Community, CommunityInsertForm, CommunityPersonBan, CommunityPersonBanForm}, instance::Instance, person::{Person, PersonInsertForm}, - post::{Post, PostInsertForm, PostLike, PostLikeForm}, + post::{Post, PostInsertForm, PostLike}, }, traits::{Bannable, Crud, Likeable}, utils::build_db_pool_for_tests, }; - use lemmy_utils::error::LemmyResult; + use lemmy_utils::{error::LemmyResult, settings::structs::Settings}; use pretty_assertions::assert_eq; use serial_test::serial; @@ -141,31 +143,24 @@ mod tests { let inserted_comment = Comment::create(pool, &comment_form, None).await?; // Timmy upvotes his own post - let timmy_post_vote_form = PostLikeForm { - post_id: inserted_post.id, - person_id: inserted_timmy.id, - score: 1, - }; - PostLike::like(pool, &timmy_post_vote_form).await?; + let settings = Settings::default(); + PostLike::like(pool, inserted_timmy.id, inserted_post.id, 1, &settings).await?; // Sara downvotes timmy's post - let sara_post_vote_form = PostLikeForm { - post_id: inserted_post.id, - person_id: inserted_sara.id, - score: -1, - }; - PostLike::like(pool, &sara_post_vote_form).await?; + PostLike::like(pool, inserted_sara.id, inserted_post.id, -1, &settings).await?; let expected_post_vote_views = [ VoteView { creator: inserted_sara.clone(), creator_banned_from_community: false, score: -1, + published: None, }, VoteView { creator: inserted_timmy.clone(), creator_banned_from_community: false, score: 1, + published: None, }, ]; @@ -173,31 +168,23 @@ mod tests { assert_eq!(read_post_vote_views, expected_post_vote_views); // Timothy votes down his own comment - let timmy_comment_vote_form = CommentLikeForm { - comment_id: inserted_comment.id, - person_id: inserted_timmy.id, - score: -1, - }; - CommentLike::like(pool, &timmy_comment_vote_form).await?; + CommentLike::like(pool, inserted_timmy.id, inserted_comment.id, -1, &settings).await?; // Sara upvotes timmy's comment - let sara_comment_vote_form = CommentLikeForm { - comment_id: inserted_comment.id, - person_id: inserted_sara.id, - score: 1, - }; - CommentLike::like(pool, &sara_comment_vote_form).await?; + CommentLike::like(pool, inserted_sara.id, inserted_comment.id, 1, &settings).await?; let expected_comment_vote_views = [ VoteView { creator: inserted_timmy.clone(), creator_banned_from_community: false, score: -1, + published: None, }, VoteView { creator: inserted_sara.clone(), creator_banned_from_community: false, score: 1, + published: None, }, ]; diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index 8c28d908a..d11bc3abc 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -128,6 +128,11 @@ pub struct DatabaseConfig { /// Maximum number of active sql connections #[default(30)] pub pool_size: usize, + + /// Set true to store the time when votes were cast. Requires more storage space but can be + /// used to detect vote manipulation. + #[default(false)] + pub store_vote_timestamps: bool, } #[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] diff --git a/migrations/2024-10-17-091053_vote-no-timestamp/down.sql b/migrations/2024-10-17-091053_vote-no-timestamp/down.sql index 928530641..4373e0cf6 100644 --- a/migrations/2024-10-17-091053_vote-no-timestamp/down.sql +++ b/migrations/2024-10-17-091053_vote-no-timestamp/down.sql @@ -1,9 +1,17 @@ +-- make published not null again and add default ALTER TABLE post_like - ADD COLUMN published timestamptz NOT NULL DEFAULT now(); + ALTER COLUMN published SET NOT NULL; + +ALTER TABLE post_like + ALTER COLUMN published SET DEFAULT now(); ALTER TABLE comment_like - ADD COLUMN published timestamptz NOT NULL DEFAULT now(); + ALTER COLUMN published DROP NOT NULL; +ALTER TABLE comment_like + ALTER COLUMN published SET DEFAULT now(); + +-- restore comment_like.post_id ALTER TABLE comment_like ADD COLUMN post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE; diff --git a/migrations/2024-10-17-091053_vote-no-timestamp/up.sql b/migrations/2024-10-17-091053_vote-no-timestamp/up.sql index 2cf906a09..65fe510cc 100644 --- a/migrations/2024-10-17-091053_vote-no-timestamp/up.sql +++ b/migrations/2024-10-17-091053_vote-no-timestamp/up.sql @@ -1,21 +1,18 @@ --- set all column values to null to reclaim disk space --- https://dba.stackexchange.com/a/117513 +-- make published columns nullable and remove default value ALTER TABLE post_like ALTER COLUMN published DROP NOT NULL; -UPDATE - post_like -SET - published = NULL; +ALTER TABLE post_like + ALTER COLUMN published DROP DEFAULT; ALTER TABLE comment_like ALTER COLUMN published DROP NOT NULL; -UPDATE - comment_like -SET - published = NULL; +ALTER TABLE comment_like + ALTER COLUMN published DROP DEFAULT; +-- get rid of comment_like.post_id, setting null first to reclaim space +-- https://dba.stackexchange.com/a/117513 ALTER TABLE comment_like ALTER COLUMN post_id DROP NOT NULL; @@ -25,12 +22,6 @@ SET post_id = NULL; -- drop the columns -ALTER TABLE post_like - DROP published; - -ALTER TABLE comment_like - DROP published; - ALTER TABLE comment_like DROP post_id;