add setting store_vote_timestamps

This commit is contained in:
Felix Ableitner 2024-10-17 15:08:56 +02:00
parent 08055b9dfb
commit fd8a6b46f7
24 changed files with 285 additions and 256 deletions

View file

@ -35,6 +35,9 @@
database: "string" database: "string"
# Maximum number of active sql connections # Maximum number of active sql connections
pool_size: 30 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 image server configuration.
pictrs: { pictrs: {

View file

@ -9,11 +9,7 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::LocalUserId, newtypes::LocalUserId,
source::{ source::{comment::CommentLike, comment_reply::CommentReply, local_site::LocalSite},
comment::{CommentLike, CommentLikeForm},
comment_reply::CommentReply,
local_site::LocalSite,
},
traits::Likeable, traits::Likeable,
}; };
use lemmy_db_views::structs::{CommentView, LocalUserView}; 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 // Remove any likes first
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
CommentLike::remove(&mut context.pool(), person_id, comment_id).await?; CommentLike::remove(&mut context.pool(), person_id, comment_id).await?;
// Only add the like if the score isnt 0 // 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 { if do_add {
CommentLike::like(&mut context.pool(), &like_form) CommentLike::like(
.await &mut context.pool(),
.with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; person_id,
data.comment_id,
data.score,
context.settings(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntLikeComment)?;
} }
ActivityChannel::submit_activity( ActivityChannel::submit_activity(

View file

@ -17,7 +17,7 @@ use lemmy_db_schema::{
source::{ source::{
community::Community, community::Community,
local_site::LocalSite, local_site::LocalSite,
post::{Post, PostLike, PostLikeForm}, post::{Post, PostLike},
}, },
traits::{Crud, Likeable}, traits::{Crud, Likeable},
}; };
@ -54,23 +54,23 @@ pub async fn like_post(
) )
.await?; .await?;
let like_form = PostLikeForm {
post_id: data.post_id,
person_id: local_user_view.person.id,
score: data.score,
};
// Remove any likes first // Remove any likes first
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
PostLike::remove(&mut context.pool(), person_id, post_id).await?; PostLike::remove(&mut context.pool(), person_id, post_id).await?;
// Only add the like if the score isnt 0 // 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 { if do_add {
PostLike::like(&mut context.pool(), &like_form) PostLike::like(
.await &mut context.pool(),
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?; 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?; mark_post_as_read(person_id, post_id, &mut context.pool()).await?;

View file

@ -19,7 +19,7 @@ use lemmy_db_schema::{
impls::actor_language::default_post_language, impls::actor_language::default_post_language,
source::{ source::{
actor_language::CommunityLanguage, actor_language::CommunityLanguage,
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm}, comment::{Comment, CommentInsertForm, CommentLike},
comment_reply::{CommentReply, CommentReplyUpdateForm}, comment_reply::{CommentReply, CommentReplyUpdateForm},
local_site::LocalSite, local_site::LocalSite,
person_mention::{PersonMention, PersonMentionUpdateForm}, person_mention::{PersonMention, PersonMentionUpdateForm},
@ -130,15 +130,15 @@ pub async fn create_comment(
.await?; .await?;
// You like your own comment by default // You like your own comment by default
let like_form = CommentLikeForm { CommentLike::like(
comment_id: inserted_comment.id, &mut context.pool(),
person_id: local_user_view.person.id, local_user_view.person.id,
score: 1, inserted_comment.id,
}; 1,
context.settings(),
CommentLike::like(&mut context.pool(), &like_form) )
.await .await
.with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?;
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::CreateComment(inserted_comment.clone()), SendActivityData::CreateComment(inserted_comment.clone()),

View file

@ -22,7 +22,7 @@ use lemmy_db_schema::{
actor_language::CommunityLanguage, actor_language::CommunityLanguage,
community::Community, community::Community,
local_site::LocalSite, local_site::LocalSite,
post::{Post, PostInsertForm, PostLike, PostLikeForm}, post::{Post, PostInsertForm, PostLike},
}, },
traits::{Crud, Likeable}, traits::{Crud, Likeable},
utils::diesel_url_create, utils::diesel_url_create,
@ -159,15 +159,15 @@ pub async fn create_post(
// They like their own post by default // They like their own post by default
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let post_id = inserted_post.id; let post_id = inserted_post.id;
let like_form = PostLikeForm { PostLike::like(
post_id, &mut context.pool(),
person_id, person_id,
score: 1, post_id,
}; 1,
context.settings(),
PostLike::like(&mut context.pool(), &like_form) )
.await .await
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?; .with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
mark_post_as_read(person_id, post_id, &mut context.pool()).await?; mark_post_as_read(person_id, post_id, &mut context.pool()).await?;

View file

@ -32,7 +32,7 @@ use lemmy_db_schema::{
newtypes::PersonId, newtypes::PersonId,
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
comment::{Comment, CommentLike, CommentLikeForm}, comment::{Comment, CommentLike},
community::Community, community::Community,
person::Person, person::Person,
post::Post, post::Post,
@ -151,12 +151,14 @@ impl ActivityHandler for CreateOrUpdateNote {
let comment = ApubComment::from_json(self.object, context).await?; let comment = ApubComment::from_json(self.object, context).await?;
// author likes their own comment by default // author likes their own comment by default
let like_form = CommentLikeForm { CommentLike::like(
comment_id: comment.id, &mut context.pool(),
person_id: comment.creator_id, comment.creator_id,
score: 1, comment.id,
}; 1,
CommentLike::like(&mut context.pool(), &like_form).await?; context.settings(),
)
.await?;
// Calculate initial hot_rank // Calculate initial hot_rank
CommentAggregates::update_hot_rank(&mut context.pool(), comment.id).await?; CommentAggregates::update_hot_rank(&mut context.pool(), comment.id).await?;

View file

@ -28,7 +28,7 @@ use lemmy_db_schema::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
community::Community, community::Community,
person::Person, person::Person,
post::{Post, PostLike, PostLikeForm}, post::{Post, PostLike},
}, },
traits::{Crud, Likeable}, traits::{Crud, Likeable},
}; };
@ -118,12 +118,14 @@ impl ActivityHandler for CreateOrUpdatePage {
let post = ApubPost::from_json(self.object, context).await?; let post = ApubPost::from_json(self.object, context).await?;
// author likes their own post by default // author likes their own post by default
let like_form = PostLikeForm { PostLike::like(
post_id: post.id, &mut context.pool(),
person_id: post.creator_id, post.creator_id,
score: 1, post.id,
}; 1,
PostLike::like(&mut context.pool(), &like_form).await?; context.settings(),
)
.await?;
// Calculate initial hot_rank for post // Calculate initial hot_rank for post
PostAggregates::update_ranks(&mut context.pool(), post.id).await?; PostAggregates::update_ranks(&mut context.pool(), post.id).await?;

View file

@ -14,10 +14,10 @@ use lemmy_db_schema::{
newtypes::DbUrl, newtypes::DbUrl,
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
comment::{CommentLike, CommentLikeForm}, comment::CommentLike,
community::Community, community::Community,
person::Person, person::Person,
post::{PostLike, PostLikeForm}, post::PostLike,
}, },
traits::Likeable, traits::Likeable,
}; };
@ -59,15 +59,15 @@ async fn vote_comment(
comment: &ApubComment, comment: &ApubComment,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let comment_id = comment.id; CommentLike::remove(&mut context.pool(), actor.id, comment.id).await?;
let like_form = CommentLikeForm { CommentLike::like(
comment_id, &mut context.pool(),
person_id: actor.id, actor.id,
score: vote_type.into(), comment.id,
}; vote_type.into(),
let person_id = actor.id; context.settings(),
CommentLike::remove(&mut context.pool(), person_id, comment_id).await?; )
CommentLike::like(&mut context.pool(), &like_form).await?; .await?;
Ok(()) Ok(())
} }
@ -78,15 +78,15 @@ async fn vote_post(
post: &ApubPost, post: &ApubPost,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let post_id = post.id; PostLike::remove(&mut context.pool(), actor.id, post.id).await?;
let like_form = PostLikeForm { PostLike::like(
post_id: post.id, &mut context.pool(),
person_id: actor.id, actor.id,
score: vote_type.into(), post.id,
}; vote_type.into(),
let person_id = actor.id; context.settings(),
PostLike::remove(&mut context.pool(), person_id, post_id).await?; )
PostLike::like(&mut context.pool(), &like_form).await?; .await?;
Ok(()) Ok(())
} }

View file

@ -35,7 +35,7 @@ mod tests {
use crate::{ use crate::{
aggregates::comment_aggregates::CommentAggregates, aggregates::comment_aggregates::CommentAggregates,
source::{ source::{
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm}, comment::{Comment, CommentInsertForm, CommentLike},
community::{Community, CommunityInsertForm}, community::{Community, CommunityInsertForm},
instance::Instance, instance::Instance,
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
@ -45,6 +45,7 @@ mod tests {
utils::build_db_pool_for_tests, utils::build_db_pool_for_tests,
}; };
use diesel::result::Error; use diesel::result::Error;
use lemmy_utils::settings::structs::Settings;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
@ -94,13 +95,8 @@ mod tests {
let _inserted_child_comment = let _inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let comment_like = CommentLikeForm { let settings = Settings::default();
comment_id: inserted_comment.id, CommentLike::like(pool, inserted_person.id, inserted_comment.id, 1, &settings).await?;
person_id: inserted_person.id,
score: 1,
};
CommentLike::like(pool, &comment_like).await?;
let comment_aggs_before_delete = CommentAggregates::read(pool, inserted_comment.id).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); assert_eq!(0, comment_aggs_before_delete.downvotes);
// Add a post dislike from the other person // Add a post dislike from the other person
let comment_dislike = CommentLikeForm { CommentLike::like(
comment_id: inserted_comment.id, pool,
person_id: another_inserted_person.id, another_inserted_person.id,
score: -1, inserted_comment.id,
}; -1,
&settings,
CommentLike::like(pool, &comment_dislike).await?; )
.await?;
let comment_aggs_after_dislike = CommentAggregates::read(pool, inserted_comment.id).await?; let comment_aggs_after_dislike = CommentAggregates::read(pool, inserted_comment.id).await?;

View file

@ -20,16 +20,17 @@ mod tests {
use crate::{ use crate::{
aggregates::person_aggregates::PersonAggregates, aggregates::person_aggregates::PersonAggregates,
source::{ source::{
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm}, comment::{Comment, CommentInsertForm, CommentLike, CommentUpdateForm},
community::{Community, CommunityInsertForm}, community::{Community, CommunityInsertForm},
instance::Instance, instance::Instance,
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
post::{Post, PostInsertForm, PostLike, PostLikeForm}, post::{Post, PostInsertForm, PostLike},
}, },
traits::{Crud, Likeable}, traits::{Crud, Likeable},
utils::build_db_pool_for_tests, utils::build_db_pool_for_tests,
}; };
use diesel::result::Error; use diesel::result::Error;
use lemmy_utils::settings::structs::Settings;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
@ -65,12 +66,8 @@ mod tests {
); );
let inserted_post = Post::create(pool, &new_post).await?; let inserted_post = Post::create(pool, &new_post).await?;
let post_like = PostLikeForm { let settings = Settings::default();
post_id: inserted_post.id, PostLike::like(pool, inserted_person.id, inserted_post.id, 1, &settings).await?;
person_id: inserted_person.id,
score: 1,
};
let _inserted_post_like = PostLike::like(pool, &post_like).await?;
let comment_form = CommentInsertForm::new( let comment_form = CommentInsertForm::new(
inserted_person.id, inserted_person.id,
@ -79,13 +76,7 @@ mod tests {
); );
let inserted_comment = Comment::create(pool, &comment_form, None).await?; let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let mut comment_like = CommentLikeForm { CommentLike::like(pool, inserted_person.id, inserted_comment.id, 1, &settings).await?;
comment_id: inserted_comment.id,
person_id: inserted_person.id,
score: 1,
};
let _inserted_comment_like = CommentLike::like(pool, &comment_like).await?;
let child_comment_form = CommentInsertForm::new( let child_comment_form = CommentInsertForm::new(
inserted_person.id, inserted_person.id,
@ -95,13 +86,14 @@ mod tests {
let inserted_child_comment = let inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let child_comment_like = CommentLikeForm { CommentLike::like(
comment_id: inserted_child_comment.id, pool,
person_id: another_inserted_person.id, another_inserted_person.id,
score: 1, inserted_child_comment.id,
}; 1,
&settings,
let _inserted_child_comment_like = CommentLike::like(pool, &child_comment_like).await?; )
.await?;
let person_aggregates_before_delete = PersonAggregates::read(pool, inserted_person.id).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_parent_comment = Comment::create(pool, &comment_form, None).await?;
let _new_child_comment = let _new_child_comment =
Comment::create(pool, &child_comment_form, Some(&new_parent_comment.path)).await?; Comment::create(pool, &child_comment_form, Some(&new_parent_comment.path)).await?;
comment_like.comment_id = new_parent_comment.id; CommentLike::like(
CommentLike::like(pool, &comment_like).await?; pool,
inserted_person.id,
new_parent_comment.id,
1,
&settings,
)
.await?;
let after_comment_add = PersonAggregates::read(pool, inserted_person.id).await?; let after_comment_add = PersonAggregates::read(pool, inserted_person.id).await?;
assert_eq!(2, after_comment_add.comment_count); assert_eq!(2, after_comment_add.comment_count);
// TODO: fix person aggregate comment score calculation // TODO: fix person aggregate comment score calculation

View file

@ -58,12 +58,13 @@ mod tests {
community::{Community, CommunityInsertForm}, community::{Community, CommunityInsertForm},
instance::Instance, instance::Instance,
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
post::{Post, PostInsertForm, PostLike, PostLikeForm}, post::{Post, PostInsertForm, PostLike},
}, },
traits::{Crud, Likeable}, traits::{Crud, Likeable},
utils::build_db_pool_for_tests, utils::build_db_pool_for_tests,
}; };
use diesel::result::Error; use diesel::result::Error;
use lemmy_utils::settings::structs::Settings;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
@ -113,13 +114,8 @@ mod tests {
let inserted_child_comment = let inserted_child_comment =
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
let post_like = PostLikeForm { let settings = Settings::default();
post_id: inserted_post.id, PostLike::like(pool, inserted_person.id, inserted_post.id, 1, &settings).await?;
person_id: inserted_person.id,
score: 1,
};
PostLike::like(pool, &post_like).await?;
let post_aggs_before_delete = PostAggregates::read(pool, inserted_post.id).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); assert_eq!(0, post_aggs_before_delete.downvotes);
// Add a post dislike from the other person // Add a post dislike from the other person
let post_dislike = PostLikeForm { PostLike::like(
post_id: inserted_post.id, pool,
person_id: another_inserted_person.id, another_inserted_person.id,
score: -1, inserted_post.id,
}; -1,
&settings,
PostLike::like(pool, &post_dislike).await?; )
.await?;
let post_aggs_after_dislike = PostAggregates::read(pool, inserted_post.id).await?; let post_aggs_after_dislike = PostAggregates::read(pool, inserted_post.id).await?;

View file

@ -18,6 +18,7 @@ use chrono::{DateTime, Utc};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use diesel_ltree::Ltree; use diesel_ltree::Ltree;
use lemmy_utils::settings::structs::Settings;
use url::Url; use url::Url;
impl Comment { impl Comment {
@ -138,16 +139,32 @@ impl Crud for Comment {
#[async_trait] #[async_trait]
impl Likeable for CommentLike { impl Likeable for CommentLike {
type Form = CommentLikeForm;
type IdType = CommentId; type IdType = CommentId;
async fn like(pool: &mut DbPool<'_>, comment_like_form: &CommentLikeForm) -> Result<Self, Error> { async fn like(
use crate::schema::comment_like::dsl::{comment_id, comment_like, person_id}; pool: &mut DbPool<'_>,
person_id: PersonId,
comment_id: Self::IdType,
score: i16,
settings: &Settings,
) -> Result<Self, Error> {
use crate::schema::comment_like;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(comment_like) let published = if settings.database.store_vote_timestamps {
.values(comment_like_form) Some(Utc::now())
.on_conflict((comment_id, person_id)) } 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() .do_update()
.set(comment_like_form) .set(&form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -205,7 +222,6 @@ mod tests {
Comment, Comment,
CommentInsertForm, CommentInsertForm,
CommentLike, CommentLike,
CommentLikeForm,
CommentSaved, CommentSaved,
CommentSavedForm, CommentSavedForm,
CommentUpdateForm, CommentUpdateForm,
@ -219,7 +235,7 @@ mod tests {
utils::build_db_pool_for_tests, utils::build_db_pool_for_tests,
}; };
use diesel_ltree::Ltree; use diesel_ltree::Ltree;
use lemmy_utils::error::LemmyResult; use lemmy_utils::{error::LemmyResult, settings::structs::Settings};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
use url::Url; use url::Url;
@ -287,18 +303,15 @@ mod tests {
Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?; Comment::create(pool, &child_comment_form, Some(&inserted_comment.path)).await?;
// Comment Like // Comment Like
let comment_like_form = CommentLikeForm { let settings = Settings::default();
comment_id: inserted_comment.id, let inserted_comment_like =
person_id: inserted_person.id, CommentLike::like(pool, inserted_person.id, inserted_comment.id, 1, &settings).await?;
score: 1,
};
let inserted_comment_like = CommentLike::like(pool, &comment_like_form).await?;
let expected_comment_like = CommentLike { let expected_comment_like = CommentLike {
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
person_id: inserted_person.id, person_id: inserted_person.id,
score: 1, score: 1,
published: None,
}; };
// Comment Saved // Comment Saved

View file

@ -39,6 +39,7 @@ use diesel::{
TextExpressionMethods, TextExpressionMethods,
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_utils::settings::structs::Settings;
use std::collections::HashSet; use std::collections::HashSet;
#[async_trait] #[async_trait]
@ -274,15 +275,31 @@ impl Post {
#[async_trait] #[async_trait]
impl Likeable for PostLike { impl Likeable for PostLike {
type Form = PostLikeForm;
type IdType = PostId; type IdType = PostId;
async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result<Self, Error> { async fn like(
pool: &mut DbPool<'_>,
person_id: PersonId,
post_id: Self::IdType,
score: i16,
settings: &Settings,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; 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) insert_into(post_like::table)
.values(post_like_form) .values(&form)
.on_conflict((post_like::post_id, post_like::person_id)) .on_conflict((post_like::post_id, post_like::person_id))
.do_update() .do_update()
.set(post_like_form) .set(&form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -399,22 +416,13 @@ mod tests {
community::{Community, CommunityInsertForm}, community::{Community, CommunityInsertForm},
instance::Instance, instance::Instance,
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
post::{ post::{Post, PostInsertForm, PostLike, PostRead, PostSaved, PostSavedForm, PostUpdateForm},
Post,
PostInsertForm,
PostLike,
PostLikeForm,
PostRead,
PostSaved,
PostSavedForm,
PostUpdateForm,
},
}, },
traits::{Crud, Likeable, Saveable}, traits::{Crud, Likeable, Saveable},
utils::build_db_pool_for_tests, utils::build_db_pool_for_tests,
}; };
use chrono::DateTime; use chrono::DateTime;
use lemmy_utils::error::LemmyResult; use lemmy_utils::{error::LemmyResult, settings::structs::Settings};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
use std::collections::HashSet; use std::collections::HashSet;
@ -489,18 +497,15 @@ mod tests {
}; };
// Post Like // Post Like
let post_like_form = PostLikeForm { let settings = Settings::default();
post_id: inserted_post.id, let inserted_post_like =
person_id: inserted_person.id, PostLike::like(pool, inserted_person.id, inserted_post.id, 1, &settings).await?;
score: 1,
};
let inserted_post_like = PostLike::like(pool, &post_like_form).await?;
let expected_post_like = PostLike { let expected_post_like = PostLike {
post_id: inserted_post.id, post_id: inserted_post.id,
person_id: inserted_person.id, person_id: inserted_person.id,
score: 1, score: 1,
published: None,
}; };
// Post Save // Post Save

View file

@ -124,6 +124,7 @@ diesel::table! {
person_id -> Int4, person_id -> Int4,
comment_id -> Int4, comment_id -> Int4,
score -> Int2, score -> Int2,
published -> Nullable<Timestamptz>,
} }
} }
@ -814,6 +815,7 @@ diesel::table! {
post_id -> Int4, post_id -> Int4,
person_id -> Int4, person_id -> Int4,
score -> Int2, score -> Int2,
published -> Nullable<Timestamptz>,
} }
} }

View file

@ -103,15 +103,17 @@ pub struct CommentLike {
pub person_id: PersonId, pub person_id: PersonId,
pub comment_id: CommentId, pub comment_id: CommentId,
pub score: i16, pub score: i16,
pub published: Option<DateTime<Utc>>,
} }
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = comment_like))] #[cfg_attr(feature = "full", diesel(table_name = comment_like))]
pub struct CommentLikeForm { pub(crate) struct CommentLikeForm {
pub person_id: PersonId, pub person_id: PersonId,
pub comment_id: CommentId, pub comment_id: CommentId,
pub score: i16, pub score: i16,
pub published: Option<DateTime<Utc>>,
} }
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]

View file

@ -150,15 +150,17 @@ pub struct PostLike {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,
pub score: i16, pub score: i16,
pub published: Option<DateTime<Utc>>,
} }
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = post_like))] #[cfg_attr(feature = "full", diesel(table_name = post_like))]
pub struct PostLikeForm { pub(crate) struct PostLikeForm {
pub post_id: PostId, pub post_id: PostId,
pub person_id: PersonId, pub person_id: PersonId,
pub score: i16, pub score: i16,
pub published: Option<DateTime<Utc>>,
} }
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]

View file

@ -15,6 +15,7 @@ use diesel_async::{
AsyncPgConnection, AsyncPgConnection,
RunQueryDsl, RunQueryDsl,
}; };
use lemmy_utils::settings::structs::Settings;
/// Returned by `diesel::delete` /// Returned by `diesel::delete`
pub type Delete<T> = DeleteStatement<<T as HasTable>::Table, <T as IntoUpdateTarget>::WhereClause>; pub type Delete<T> = DeleteStatement<<T as HasTable>::Table, <T as IntoUpdateTarget>::WhereClause>;
@ -94,9 +95,14 @@ pub trait Joinable {
#[async_trait] #[async_trait]
pub trait Likeable { pub trait Likeable {
type Form;
type IdType; type IdType;
async fn like(pool: &mut DbPool<'_>, form: &Self::Form) -> Result<Self, Error> async fn like(
pool: &mut DbPool<'_>,
person_id: PersonId,
item_id: Self::IdType,
score: i16,
settings: &Settings,
) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
async fn remove( async fn remove(

View file

@ -438,7 +438,6 @@ mod tests {
Comment, Comment,
CommentInsertForm, CommentInsertForm,
CommentLike, CommentLike,
CommentLikeForm,
CommentSaved, CommentSaved,
CommentSavedForm, CommentSavedForm,
CommentUpdateForm, CommentUpdateForm,
@ -466,7 +465,7 @@ mod tests {
CommunityVisibility, CommunityVisibility,
SubscribedType, SubscribedType,
}; };
use lemmy_utils::error::LemmyResult; use lemmy_utils::{error::LemmyResult, settings::structs::Settings};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
@ -599,13 +598,15 @@ mod tests {
}; };
assert_eq!(expected_block, inserted_block); assert_eq!(expected_block, inserted_block);
let comment_like_form = CommentLikeForm { let settings = Settings::default();
comment_id: inserted_comment_0.id, CommentLike::like(
person_id: inserted_timmy_person.id, pool,
score: 1, inserted_timmy_person.id,
}; inserted_comment_0.id,
1,
let _inserted_comment_like = CommentLike::like(pool, &comment_like_form).await?; &settings,
)
.await?;
let timmy_local_user_view = LocalUserView { let timmy_local_user_view = LocalUserView {
local_user: inserted_timmy_local_user.clone(), local_user: inserted_timmy_local_user.clone(),
@ -698,12 +699,15 @@ mod tests {
PersonBlock::unblock(pool, &timmy_unblocks_sara_form).await?; PersonBlock::unblock(pool, &timmy_unblocks_sara_form).await?;
// Like a new comment // Like a new comment
let comment_like_form = CommentLikeForm { let settings = Settings::default();
comment_id: data.inserted_comment_1.id, CommentLike::like(
person_id: data.timmy_local_user_view.person.id, pool,
score: 1, data.timmy_local_user_view.person.id,
}; data.inserted_comment_1.id,
CommentLike::like(pool, &comment_like_form).await?; 1,
&settings,
)
.await?;
let read_liked_comment_views = CommentQuery { let read_liked_comment_views = CommentQuery {
local_user: Some(&data.timmy_local_user_view.local_user), local_user: Some(&data.timmy_local_user_view.local_user),

View file

@ -759,7 +759,6 @@ mod tests {
PostHide, PostHide,
PostInsertForm, PostInsertForm,
PostLike, PostLike,
PostLikeForm,
PostRead, PostRead,
PostSaved, PostSaved,
PostSavedForm, PostSavedForm,
@ -773,7 +772,7 @@ mod tests {
PostSortType, PostSortType,
SubscribedType, SubscribedType,
}; };
use lemmy_utils::error::LemmyResult; use lemmy_utils::{error::LemmyResult, settings::structs::Settings};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
use std::{collections::HashSet, time::Duration}; use std::{collections::HashSet, time::Duration};
@ -1114,18 +1113,21 @@ mod tests {
let pool = &mut pool.into(); let pool = &mut pool.into();
let mut data = init_data(pool).await?; let mut data = init_data(pool).await?;
let post_like_form = PostLikeForm { let settings = Settings::default();
post_id: data.inserted_post.id, let inserted_post_like = PostLike::like(
person_id: data.local_user_view.person.id, pool,
score: 1, data.local_user_view.person.id,
}; data.inserted_post.id,
1,
let inserted_post_like = PostLike::like(pool, &post_like_form).await?; &settings,
)
.await?;
let expected_post_like = PostLike { let expected_post_like = PostLike {
post_id: data.inserted_post.id, post_id: data.inserted_post.id,
person_id: data.local_user_view.person.id, person_id: data.local_user_view.person.id,
score: 1, score: 1,
published: None,
}; };
assert_eq!(expected_post_like, inserted_post_like); assert_eq!(expected_post_like, inserted_post_like);
@ -1173,19 +1175,24 @@ mod tests {
// Like both the bot post, and your own // Like both the bot post, and your own
// The liked_only should not show your own post // The liked_only should not show your own post
let post_like_form = PostLikeForm { let settings = Settings::default();
post_id: data.inserted_post.id, PostLike::like(
person_id: data.local_user_view.person.id, pool,
score: 1, data.local_user_view.person.id,
}; data.inserted_post.id,
PostLike::like(pool, &post_like_form).await?; 1,
&settings,
)
.await?;
let bot_post_like_form = PostLikeForm { PostLike::like(
post_id: data.inserted_bot_post.id, pool,
person_id: data.local_user_view.person.id, data.local_user_view.person.id,
score: 1, data.inserted_bot_post.id,
}; 1,
PostLike::like(pool, &bot_post_like_form).await?; &settings,
)
.await?;
// Read the liked only // Read the liked only
let read_liked_post_listing = PostQuery { let read_liked_post_listing = PostQuery {

View file

@ -1,3 +1,4 @@
use chrono::{DateTime, Utc};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use diesel::Queryable; use diesel::Queryable;
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -216,6 +217,7 @@ pub struct VoteView {
pub creator: Person, pub creator: Person,
pub creator_banned_from_community: bool, pub creator_banned_from_community: bool,
pub score: i16, pub score: i16,
pub published: Option<DateTime<Utc>>,
} }
#[skip_serializing_none] #[skip_serializing_none]

View file

@ -40,6 +40,7 @@ impl VoteView {
person::all_columns, person::all_columns,
community_person_ban::community_id.nullable().is_not_null(), community_person_ban::community_id.nullable().is_not_null(),
post_like::score, post_like::score,
post_like::published,
)) ))
.order_by(post_like::score) .order_by(post_like::score)
.limit(limit) .limit(limit)
@ -74,6 +75,7 @@ impl VoteView {
person::all_columns, person::all_columns,
community_person_ban::community_id.nullable().is_not_null(), community_person_ban::community_id.nullable().is_not_null(),
comment_like::score, comment_like::score,
comment_like::published,
)) ))
.order_by(comment_like::score) .order_by(comment_like::score)
.limit(limit) .limit(limit)
@ -89,16 +91,16 @@ mod tests {
use crate::structs::VoteView; use crate::structs::VoteView;
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm}, comment::{Comment, CommentInsertForm, CommentLike},
community::{Community, CommunityInsertForm, CommunityPersonBan, CommunityPersonBanForm}, community::{Community, CommunityInsertForm, CommunityPersonBan, CommunityPersonBanForm},
instance::Instance, instance::Instance,
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
post::{Post, PostInsertForm, PostLike, PostLikeForm}, post::{Post, PostInsertForm, PostLike},
}, },
traits::{Bannable, Crud, Likeable}, traits::{Bannable, Crud, Likeable},
utils::build_db_pool_for_tests, 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 pretty_assertions::assert_eq;
use serial_test::serial; use serial_test::serial;
@ -141,31 +143,24 @@ mod tests {
let inserted_comment = Comment::create(pool, &comment_form, None).await?; let inserted_comment = Comment::create(pool, &comment_form, None).await?;
// Timmy upvotes his own post // Timmy upvotes his own post
let timmy_post_vote_form = PostLikeForm { let settings = Settings::default();
post_id: inserted_post.id, PostLike::like(pool, inserted_timmy.id, inserted_post.id, 1, &settings).await?;
person_id: inserted_timmy.id,
score: 1,
};
PostLike::like(pool, &timmy_post_vote_form).await?;
// Sara downvotes timmy's post // Sara downvotes timmy's post
let sara_post_vote_form = PostLikeForm { PostLike::like(pool, inserted_sara.id, inserted_post.id, -1, &settings).await?;
post_id: inserted_post.id,
person_id: inserted_sara.id,
score: -1,
};
PostLike::like(pool, &sara_post_vote_form).await?;
let expected_post_vote_views = [ let expected_post_vote_views = [
VoteView { VoteView {
creator: inserted_sara.clone(), creator: inserted_sara.clone(),
creator_banned_from_community: false, creator_banned_from_community: false,
score: -1, score: -1,
published: None,
}, },
VoteView { VoteView {
creator: inserted_timmy.clone(), creator: inserted_timmy.clone(),
creator_banned_from_community: false, creator_banned_from_community: false,
score: 1, score: 1,
published: None,
}, },
]; ];
@ -173,31 +168,23 @@ mod tests {
assert_eq!(read_post_vote_views, expected_post_vote_views); assert_eq!(read_post_vote_views, expected_post_vote_views);
// Timothy votes down his own comment // Timothy votes down his own comment
let timmy_comment_vote_form = CommentLikeForm { CommentLike::like(pool, inserted_timmy.id, inserted_comment.id, -1, &settings).await?;
comment_id: inserted_comment.id,
person_id: inserted_timmy.id,
score: -1,
};
CommentLike::like(pool, &timmy_comment_vote_form).await?;
// Sara upvotes timmy's comment // Sara upvotes timmy's comment
let sara_comment_vote_form = CommentLikeForm { CommentLike::like(pool, inserted_sara.id, inserted_comment.id, 1, &settings).await?;
comment_id: inserted_comment.id,
person_id: inserted_sara.id,
score: 1,
};
CommentLike::like(pool, &sara_comment_vote_form).await?;
let expected_comment_vote_views = [ let expected_comment_vote_views = [
VoteView { VoteView {
creator: inserted_timmy.clone(), creator: inserted_timmy.clone(),
creator_banned_from_community: false, creator_banned_from_community: false,
score: -1, score: -1,
published: None,
}, },
VoteView { VoteView {
creator: inserted_sara.clone(), creator: inserted_sara.clone(),
creator_banned_from_community: false, creator_banned_from_community: false,
score: 1, score: 1,
published: None,
}, },
]; ];

View file

@ -128,6 +128,11 @@ pub struct DatabaseConfig {
/// Maximum number of active sql connections /// Maximum number of active sql connections
#[default(30)] #[default(30)]
pub pool_size: usize, 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)] #[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)]

View file

@ -1,9 +1,17 @@
-- make published not null again and add default
ALTER TABLE post_like 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 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 ALTER TABLE comment_like
ADD COLUMN post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE; ADD COLUMN post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE;

View file

@ -1,21 +1,18 @@
-- set all column values to null to reclaim disk space -- make published columns nullable and remove default value
-- https://dba.stackexchange.com/a/117513
ALTER TABLE post_like ALTER TABLE post_like
ALTER COLUMN published DROP NOT NULL; ALTER COLUMN published DROP NOT NULL;
UPDATE ALTER TABLE post_like
post_like ALTER COLUMN published DROP DEFAULT;
SET
published = NULL;
ALTER TABLE comment_like ALTER TABLE comment_like
ALTER COLUMN published DROP NOT NULL; ALTER COLUMN published DROP NOT NULL;
UPDATE ALTER TABLE comment_like
comment_like ALTER COLUMN published DROP DEFAULT;
SET
published = NULL;
-- get rid of comment_like.post_id, setting null first to reclaim space
-- https://dba.stackexchange.com/a/117513
ALTER TABLE comment_like ALTER TABLE comment_like
ALTER COLUMN post_id DROP NOT NULL; ALTER COLUMN post_id DROP NOT NULL;
@ -25,12 +22,6 @@ SET
post_id = NULL; post_id = NULL;
-- drop the columns -- drop the columns
ALTER TABLE post_like
DROP published;
ALTER TABLE comment_like
DROP published;
ALTER TABLE comment_like ALTER TABLE comment_like
DROP post_id; DROP post_id;