diff --git a/Cargo.lock b/Cargo.lock index 4a32818e9..2396158cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1510,6 +1510,12 @@ dependencies = [ "syn 2.0.40", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -2540,6 +2546,7 @@ dependencies = [ "lemmy_db_views_actor", "lemmy_db_views_moderator", "lemmy_utils", + "pretty_assertions", "serial_test", "sitemap-rs", "tokio", @@ -2570,6 +2577,7 @@ dependencies = [ "lemmy_utils", "mime", "once_cell", + "pretty_assertions", "regex", "reqwest", "reqwest-middleware", @@ -2634,6 +2642,7 @@ dependencies = [ "lemmy_utils", "moka", "once_cell", + "pretty_assertions", "reqwest", "serde", "serde_json", @@ -2665,6 +2674,7 @@ dependencies = [ "futures-util", "lemmy_utils", "once_cell", + "pretty_assertions", "regex", "rustls 0.21.10", "serde", @@ -2694,6 +2704,7 @@ dependencies = [ "diesel_ltree", "lemmy_db_schema", "lemmy_utils", + "pretty_assertions", "serde", "serde_with", "serial_test", @@ -2710,6 +2721,7 @@ dependencies = [ "diesel", "diesel-async", "lemmy_db_schema", + "pretty_assertions", "serde", "serde_with", "serial_test", @@ -2805,6 +2817,7 @@ dependencies = [ "opentelemetry 0.19.0", "opentelemetry-otlp 0.12.0", "pict-rs", + "pretty_assertions", "prometheus", "reqwest", "reqwest-middleware", @@ -2839,6 +2852,7 @@ dependencies = [ "markdown-it", "once_cell", "openssl", + "pretty_assertions", "regex", "reqwest", "reqwest-middleware", @@ -3946,6 +3960,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.70" @@ -6447,6 +6471,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zerocopy" version = "0.7.30" diff --git a/Cargo.toml b/Cargo.toml index c47625d9b..ee8510f47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -156,6 +156,7 @@ tokio-postgres-rustls = "0.10.0" urlencoding = "2.1.3" enum-map = "2.7" moka = { version = "0.12.1", features = ["future"] } +pretty_assertions = "1.4.0" [dependencies] lemmy_api = { workspace = true } @@ -194,3 +195,6 @@ prometheus = { version = "0.13.3", features = ["process"] } serial_test = { workspace = true } clap = { version = "4.4.11", features = ["derive"] } actix-web-prom = "0.7.0" + +[dev-dependencies] +pretty_assertions = { workspace = true } diff --git a/api_tests/src/user.spec.ts b/api_tests/src/user.spec.ts index ccfc5e1fe..527ab74d4 100644 --- a/api_tests/src/user.spec.ts +++ b/api_tests/src/user.spec.ts @@ -112,18 +112,19 @@ test("Delete user", async () => { ).toBe(true); }); -test("Requests with invalid auth should be treated as unauthenticated", async () => { +test("Requests with invalid auth should throw error", async () => { let invalid_auth = new LemmyHttp(alphaUrl, { headers: { Authorization: "Bearer foobar" }, fetchFunction, }); - let site = await getSite(invalid_auth); - expect(site.my_user).toBeUndefined(); - expect(site.site_view).toBeDefined(); + await expect(getSite(invalid_auth)).rejects.toStrictEqual( + Error("incorrect_login"), + ); let form: GetPosts = {}; - let posts = invalid_auth.getPosts(form); - expect((await posts).posts).toBeDefined(); + await expect(invalid_auth.getPosts(form)).rejects.toStrictEqual( + Error("incorrect_login"), + ); }); test("Create user with Arabic name", async () => { diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index f22c9192a..2f39166c7 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -42,3 +42,4 @@ actix-web-httpauth = "0.8.1" serial_test = { workspace = true } tokio = { workspace = true } elementtree = "1.2.3" +pretty_assertions = { workspace = true } diff --git a/crates/api/src/comment/list_comment_likes.rs b/crates/api/src/comment/list_comment_likes.rs new file mode 100644 index 000000000..cb487c9f1 --- /dev/null +++ b/crates/api/src/comment/list_comment_likes.rs @@ -0,0 +1,24 @@ +use actix_web::web::{Data, Json, Query}; +use lemmy_api_common::{ + comment::{ListCommentLikes, ListCommentLikesResponse}, + context::LemmyContext, + utils::is_admin, +}; +use lemmy_db_views::structs::{LocalUserView, VoteView}; +use lemmy_utils::error::LemmyError; + +/// Lists likes for a comment +#[tracing::instrument(skip(context))] +pub async fn list_comment_likes( + data: Query, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + let comment_likes = + VoteView::list_for_comment(&mut context.pool(), data.comment_id, data.page, data.limit).await?; + + Ok(Json(ListCommentLikesResponse { comment_likes })) +} diff --git a/crates/api/src/comment/mod.rs b/crates/api/src/comment/mod.rs index 8caeaf8b0..9830e295d 100644 --- a/crates/api/src/comment/mod.rs +++ b/crates/api/src/comment/mod.rs @@ -1,3 +1,4 @@ pub mod distinguish; pub mod like; +pub mod list_comment_likes; pub mod save; diff --git a/crates/api/src/comment_report/resolve.rs b/crates/api/src/comment_report/resolve.rs index 41ebe0d00..70e7d89f6 100644 --- a/crates/api/src/comment_report/resolve.rs +++ b/crates/api/src/comment_report/resolve.rs @@ -23,7 +23,7 @@ pub async fn resolve_comment_report( check_community_mod_action( &local_user_view.person, report.community.id, - false, + true, &mut context.pool(), ) .await?; diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index faa74824e..7d85cc78f 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -2,15 +2,11 @@ use actix_web::{http::header::Header, HttpRequest}; use actix_web_httpauth::headers::authorization::{Authorization, Bearer}; use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine}; use captcha::Captcha; -use lemmy_api_common::{ - claims::Claims, - context::LemmyContext, - utils::{check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME}, -}; +use lemmy_api_common::utils::{local_site_to_slur_regex, AUTH_COOKIE_NAME}; use lemmy_db_schema::source::local_site::LocalSite; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ - error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult}, + error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::slurs::check_slurs, }; use std::io::Cursor; @@ -141,20 +137,6 @@ pub(crate) fn build_totp_2fa( .with_lemmy_type(LemmyErrorType::CouldntGenerateTotp) } -#[tracing::instrument(skip_all)] -pub async fn local_user_view_from_jwt( - jwt: &str, - context: &LemmyContext, -) -> Result { - let local_user_id = Claims::validate(jwt, context) - .await - .with_lemmy_type(LemmyErrorType::NotLoggedIn)?; - let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?; - check_user_valid(&local_user_view.person)?; - - Ok(local_user_view) -} - #[cfg(test)] mod tests { #![allow(clippy::unwrap_used)] diff --git a/crates/api/src/local_user/validate_auth.rs b/crates/api/src/local_user/validate_auth.rs index d95195dc9..65c63e585 100644 --- a/crates/api/src/local_user/validate_auth.rs +++ b/crates/api/src/local_user/validate_auth.rs @@ -1,10 +1,10 @@ -use crate::{local_user_view_from_jwt, read_auth_token}; +use crate::read_auth_token; use actix_web::{ web::{Data, Json}, HttpRequest, }; -use lemmy_api_common::{context::LemmyContext, SuccessResponse}; -use lemmy_utils::error::{LemmyError, LemmyErrorType}; +use lemmy_api_common::{claims::Claims, context::LemmyContext, SuccessResponse}; +use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; /// Returns an error message if the auth token is invalid for any reason. Necessary because other /// endpoints silently treat any call with invalid auth as unauthenticated. @@ -15,7 +15,9 @@ pub async fn validate_auth( ) -> Result, LemmyError> { let jwt = read_auth_token(&req)?; if let Some(jwt) = jwt { - local_user_view_from_jwt(&jwt, &context).await?; + Claims::validate(&jwt, &context) + .await + .with_lemmy_type(LemmyErrorType::NotLoggedIn)?; } else { Err(LemmyErrorType::NotLoggedIn)?; } diff --git a/crates/api/src/post/list_post_likes.rs b/crates/api/src/post/list_post_likes.rs new file mode 100644 index 000000000..0e52052df --- /dev/null +++ b/crates/api/src/post/list_post_likes.rs @@ -0,0 +1,24 @@ +use actix_web::web::{Data, Json, Query}; +use lemmy_api_common::{ + context::LemmyContext, + post::{ListPostLikes, ListPostLikesResponse}, + utils::is_admin, +}; +use lemmy_db_views::structs::{LocalUserView, VoteView}; +use lemmy_utils::error::LemmyError; + +/// Lists likes for a post +#[tracing::instrument(skip(context))] +pub async fn list_post_likes( + data: Query, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + let post_likes = + VoteView::list_for_post(&mut context.pool(), data.post_id, data.page, data.limit).await?; + + Ok(Json(ListPostLikesResponse { post_likes })) +} diff --git a/crates/api/src/post/mod.rs b/crates/api/src/post/mod.rs index a3b84134f..6a6ed9d21 100644 --- a/crates/api/src/post/mod.rs +++ b/crates/api/src/post/mod.rs @@ -1,6 +1,7 @@ pub mod feature; pub mod get_link_metadata; pub mod like; +pub mod list_post_likes; pub mod lock; pub mod mark_read; pub mod save; diff --git a/crates/api/src/post_report/resolve.rs b/crates/api/src/post_report/resolve.rs index 3604055fd..ab6688012 100644 --- a/crates/api/src/post_report/resolve.rs +++ b/crates/api/src/post_report/resolve.rs @@ -23,7 +23,7 @@ pub async fn resolve_post_report( check_community_mod_action( &local_user_view.person, report.community.id, - false, + true, &mut context.pool(), ) .await?; diff --git a/crates/api/src/sitemap.rs b/crates/api/src/sitemap.rs index b1b961350..ec32f837f 100644 --- a/crates/api/src/sitemap.rs +++ b/crates/api/src/sitemap.rs @@ -49,6 +49,7 @@ pub(crate) mod tests { use chrono::{DateTime, NaiveDate, Utc}; use elementtree::Element; use lemmy_db_schema::newtypes::DbUrl; + use pretty_assertions::assert_eq; use url::Url; #[tokio::test] diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml index 027a4f693..901c7672f 100644 --- a/crates/api_common/Cargo.toml +++ b/crates/api_common/Cargo.toml @@ -82,3 +82,4 @@ ignored = ["getrandom"] serial_test = { workspace = true } reqwest-middleware = { workspace = true } serde_json = { workspace = true } +pretty_assertions = { workspace = true } diff --git a/crates/api_common/src/claims.rs b/crates/api_common/src/claims.rs index 09191ad71..58541af43 100644 --- a/crates/api_common/src/claims.rs +++ b/crates/api_common/src/claims.rs @@ -6,7 +6,7 @@ use lemmy_db_schema::{ newtypes::LocalUserId, source::login_token::{LoginToken, LoginTokenCreateForm}, }; -use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] @@ -25,8 +25,7 @@ impl Claims { validation.required_spec_claims.remove("exp"); let jwt_secret = &context.secret().jwt_secret; let key = DecodingKey::from_secret(jwt_secret.as_ref()); - let claims = - decode::(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?; + let claims = decode::(jwt, &key, &validation)?; let user_id = LocalUserId(claims.claims.sub.parse()?); let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?; if !is_valid { @@ -89,6 +88,7 @@ mod tests { utils::build_db_pool_for_tests, }; use lemmy_utils::rate_limit::RateLimitCell; + use pretty_assertions::assert_eq; use reqwest::Client; use reqwest_middleware::ClientBuilder; use serial_test::serial; diff --git a/crates/api_common/src/comment.rs b/crates/api_common/src/comment.rs index c2589fb2a..003e84d38 100644 --- a/crates/api_common/src/comment.rs +++ b/crates/api_common/src/comment.rs @@ -3,7 +3,7 @@ use lemmy_db_schema::{ CommentSortType, ListingType, }; -use lemmy_db_views::structs::{CommentReportView, CommentView}; +use lemmy_db_views::structs::{CommentReportView, CommentView, VoteView}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -176,3 +176,22 @@ pub struct ListCommentReports { pub struct ListCommentReportsResponse { pub comment_reports: Vec, } + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// List comment likes. Admins-only. +pub struct ListCommentLikes { + pub comment_id: CommentId, + pub page: Option, + pub limit: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The comment likes response +pub struct ListCommentLikesResponse { + pub comment_likes: Vec, +} diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index a82b9327e..2b69a13ff 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -43,5 +43,5 @@ impl Default for SuccessResponse { /// how long to sleep based on how many retries have already happened pub fn federate_retry_sleep_duration(retry_count: i32) -> Duration { - Duration::from_secs_f64(10.0 * 2.0_f64.powf(f64::from(retry_count))) + Duration::from_secs_f64(2.0_f64.powf(f64::from(retry_count))) } diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 62a4063eb..b9dfa9ef0 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -4,7 +4,7 @@ use lemmy_db_schema::{ PostFeatureType, SortType, }; -use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView}; +use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView}; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -255,3 +255,22 @@ pub struct LinkMetadata { #[serde(skip)] pub thumbnail: Option, } + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// List post likes. Admins-only. +pub struct ListPostLikes { + pub post_id: PostId, + pub page: Option, + pub limit: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The post likes response +pub struct ListPostLikesResponse { + pub post_likes: Vec, +} diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs index 77e2f697f..435411f4a 100644 --- a/crates/api_common/src/request.rs +++ b/crates/api_common/src/request.rs @@ -300,6 +300,7 @@ mod tests { context::LemmyContext, request::{extract_opengraph_data, fetch_link_metadata}, }; + use pretty_assertions::assert_eq; use serial_test::serial; use url::Url; diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index aee25f7a5..5e37877d2 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -934,6 +934,7 @@ mod tests { use super::*; use crate::utils::{honeypot_check, limit_expire_time, password_length_check}; use chrono::{Days, Utc}; + use pretty_assertions::assert_eq; use serial_test::serial; #[test] diff --git a/crates/apub/Cargo.toml b/crates/apub/Cargo.toml index 2f865ed4e..175efbd45 100644 --- a/crates/apub/Cargo.toml +++ b/crates/apub/Cargo.toml @@ -51,3 +51,4 @@ enum_delegate = "0.2.0" [dev-dependencies] serial_test = { workspace = true } assert-json-diff = "2.0.2" +pretty_assertions = { workspace = true } diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs index 7da5ac8ae..974810f61 100644 --- a/crates/apub/src/activities/community/report.rs +++ b/crates/apub/src/activities/community/report.rs @@ -1,7 +1,7 @@ use crate::{ activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community}, insert_received_activity, - objects::{community::ApubCommunity, person::ApubPerson}, + objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}, protocol::{activities::community::report::Report, InCommunity}, PostOrComment, }; @@ -19,8 +19,9 @@ use lemmy_db_schema::{ community::Community, person::Person, post_report::{PostReport, PostReportForm}, + site::Site, }, - traits::Reportable, + traits::{Crud, Reportable}, }; use lemmy_utils::error::LemmyError; use url::Url; @@ -44,18 +45,31 @@ impl Report { let report = Report { actor: actor.id().into(), to: [community.id().into()], - object: object_id, + object: object_id.clone(), summary: reason, kind, id: id.clone(), audience: Some(community.id().into()), }; - let inbox = if community.local { - ActivitySendTargets::empty() - } else { - ActivitySendTargets::to_inbox(community.shared_inbox_or_inbox()) + + // send report to the community where object was posted + let mut inboxes = ActivitySendTargets::to_inbox(community.shared_inbox_or_inbox()); + + // also send report to user's home instance if possible + let object_creator_id = match object_id.dereference_local(&context).await? { + PostOrComment::Post(p) => p.creator_id, + PostOrComment::Comment(c) => c.creator_id, }; - send_lemmy_activity(&context, report, &actor, inbox, false).await + let object_creator = Person::read(&mut context.pool(), object_creator_id).await?; + let object_creator_site: Option = + Site::read_from_instance_id(&mut context.pool(), object_creator.instance_id) + .await? + .map(Into::into); + if let Some(inbox) = object_creator_site.map(|s| s.shared_inbox_or_inbox()) { + inboxes.add_inbox(inbox); + } + + send_lemmy_activity(&context, report, &actor, inboxes, false).await } } diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index 484f0ae6b..c7e9e198e 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -313,6 +313,7 @@ mod tests { use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::CommunityFollowerView; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; + use pretty_assertions::assert_eq; use serial_test::serial; use std::time::Duration; use tokio::time::sleep; diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index dc36ebb63..5fa32e1d8 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -119,6 +119,7 @@ mod tests { traits::Crud, }; use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 9aea8adf7..81a618bba 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -200,6 +200,7 @@ pub(crate) mod tests { use html2md::parse_html; use lemmy_db_schema::source::site::Site; use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; use serial_test::serial; async fn prepare_comment_test( diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index edf9e3010..ef9088a3b 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -264,6 +264,7 @@ pub(crate) mod tests { use activitypub_federation::fetch::collection_id::CollectionId; use lemmy_db_schema::{source::site::Site, traits::Crud}; use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; use serial_test::serial; pub(crate) async fn parse_lemmy_community( diff --git a/crates/apub/src/objects/instance.rs b/crates/apub/src/objects/instance.rs index 50f2090d6..c7d4f11f6 100644 --- a/crates/apub/src/objects/instance.rs +++ b/crates/apub/src/objects/instance.rs @@ -218,6 +218,7 @@ pub(crate) mod tests { use crate::protocol::tests::file_to_json_object; use lemmy_db_schema::traits::Crud; use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; use serial_test::serial; pub(crate) async fn parse_lemmy_instance(context: &Data) -> LemmyResult { diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 880f30801..8e0d335bc 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -227,6 +227,7 @@ pub(crate) mod tests { use activitypub_federation::fetch::object_id::ObjectId; use lemmy_db_schema::{source::site::Site, traits::Crud}; use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; use serial_test::serial; pub(crate) async fn parse_lemmy_person( diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index dd4df7488..fa65500cb 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -303,6 +303,7 @@ mod tests { }; use lemmy_db_schema::source::site::Site; use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index d5b9e5185..d5c00632f 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -159,6 +159,7 @@ mod tests { use assert_json_diff::assert_json_include; use lemmy_db_schema::source::site::Site; use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; use serial_test::serial; async fn prepare_comment_test( diff --git a/crates/apub/src/protocol/collections/mod.rs b/crates/apub/src/protocol/collections/mod.rs index 7e27d7e06..0fb24349c 100644 --- a/crates/apub/src/protocol/collections/mod.rs +++ b/crates/apub/src/protocol/collections/mod.rs @@ -17,6 +17,7 @@ mod tests { tests::{test_json, test_parse_lemmy_item}, }; use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; #[test] fn test_parse_lemmy_collections() -> LemmyResult<()> { diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index fc2957e8f..b3a93b9c6 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -79,6 +79,7 @@ typed-builder = "0.15.2" [dev-dependencies] serial_test = { workspace = true } +pretty_assertions = { workspace = true } [package.metadata.cargo-machete] ignored = ["strum"] diff --git a/crates/db_schema/src/aggregates/comment_aggregates.rs b/crates/db_schema/src/aggregates/comment_aggregates.rs index 110532c5a..78ceaa0c6 100644 --- a/crates/db_schema/src/aggregates/comment_aggregates.rs +++ b/crates/db_schema/src/aggregates/comment_aggregates.rs @@ -49,6 +49,7 @@ mod tests { traits::{Crud, Likeable}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/aggregates/community_aggregates.rs b/crates/db_schema/src/aggregates/community_aggregates.rs index f61a95c7d..f4202738d 100644 --- a/crates/db_schema/src/aggregates/community_aggregates.rs +++ b/crates/db_schema/src/aggregates/community_aggregates.rs @@ -47,6 +47,7 @@ mod tests { traits::{Crud, Followable}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index d3065daf3..b48b37641 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -34,6 +34,7 @@ mod tests { traits::{Crud, Likeable}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/aggregates/post_aggregates.rs b/crates/db_schema/src/aggregates/post_aggregates.rs index a8efe9581..9415f8971 100644 --- a/crates/db_schema/src/aggregates/post_aggregates.rs +++ b/crates/db_schema/src/aggregates/post_aggregates.rs @@ -68,6 +68,7 @@ mod tests { traits::{Crud, Likeable}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/aggregates/site_aggregates.rs b/crates/db_schema/src/aggregates/site_aggregates.rs index 4fe57720a..453430841 100644 --- a/crates/db_schema/src/aggregates/site_aggregates.rs +++ b/crates/db_schema/src/aggregates/site_aggregates.rs @@ -31,6 +31,7 @@ mod tests { traits::Crud, utils::{build_db_pool_for_tests, DbPool}, }; + use pretty_assertions::assert_eq; use serial_test::serial; async fn prepare_site_with_community( diff --git a/crates/db_schema/src/impls/activity.rs b/crates/db_schema/src/impls/activity.rs index fe1a521a9..bb17c83e7 100644 --- a/crates/db_schema/src/impls/activity.rs +++ b/crates/db_schema/src/impls/activity.rs @@ -67,6 +67,7 @@ mod tests { use super::*; use crate::{source::activity::ActorType, utils::build_db_pool_for_tests}; + use pretty_assertions::assert_eq; use serde_json::json; use serial_test::serial; use url::Url; diff --git a/crates/db_schema/src/impls/actor_language.rs b/crates/db_schema/src/impls/actor_language.rs index 5c4e252d4..08ebcbd05 100644 --- a/crates/db_schema/src/impls/actor_language.rs +++ b/crates/db_schema/src/impls/actor_language.rs @@ -412,6 +412,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; async fn test_langs1(pool: &mut DbPool<'_>) -> Vec { diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 7173b79d2..c8a389475 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -263,6 +263,7 @@ mod tests { utils::build_db_pool_for_tests, }; use diesel_ltree::Ltree; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/impls/comment_reply.rs b/crates/db_schema/src/impls/comment_reply.rs index c5b5a3c6a..d76b46b8a 100644 --- a/crates/db_schema/src/impls/comment_reply.rs +++ b/crates/db_schema/src/impls/comment_reply.rs @@ -89,6 +89,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 0d2cc88ba..80b9e2496 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -366,6 +366,7 @@ mod tests { traits::{Bannable, Crud, Followable, Joinable}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/impls/federation_allowlist.rs b/crates/db_schema/src/impls/federation_allowlist.rs index eb67acce8..0664a8a4f 100644 --- a/crates/db_schema/src/impls/federation_allowlist.rs +++ b/crates/db_schema/src/impls/federation_allowlist.rs @@ -56,6 +56,7 @@ mod tests { source::{federation_allowlist::FederationAllowList, instance::Instance}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/impls/language.rs b/crates/db_schema/src/impls/language.rs index db72b837f..66fc05729 100644 --- a/crates/db_schema/src/impls/language.rs +++ b/crates/db_schema/src/impls/language.rs @@ -46,6 +46,7 @@ mod tests { #![allow(clippy::indexing_slicing)] use crate::{source::language::Language, utils::build_db_pool_for_tests}; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/impls/moderator.rs b/crates/db_schema/src/impls/moderator.rs index 012e05394..cfb64d883 100644 --- a/crates/db_schema/src/impls/moderator.rs +++ b/crates/db_schema/src/impls/moderator.rs @@ -500,6 +500,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/impls/password_reset_request.rs b/crates/db_schema/src/impls/password_reset_request.rs index 5600ffc66..6fcf86014 100644 --- a/crates/db_schema/src/impls/password_reset_request.rs +++ b/crates/db_schema/src/impls/password_reset_request.rs @@ -95,6 +95,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index e36e7b35e..32ce6c97a 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -191,6 +191,7 @@ mod tests { traits::{Crud, Followable}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/impls/person_mention.rs b/crates/db_schema/src/impls/person_mention.rs index f2441f00c..75950e7e9 100644 --- a/crates/db_schema/src/impls/person_mention.rs +++ b/crates/db_schema/src/impls/person_mention.rs @@ -90,6 +90,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index caa54c167..f49af6226 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -366,6 +366,7 @@ mod tests { traits::{Crud, Likeable, Saveable}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; use std::collections::HashSet; diff --git a/crates/db_schema/src/impls/post_report.rs b/crates/db_schema/src/impls/post_report.rs index b4078d950..b0071f965 100644 --- a/crates/db_schema/src/impls/post_report.rs +++ b/crates/db_schema/src/impls/post_report.rs @@ -75,6 +75,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; async fn init(pool: &mut DbPool<'_>) -> (Person, PostReport) { diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index 81a4b1850..961e36518 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -87,6 +87,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 7c5e05dd7..54d642034 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -439,6 +439,7 @@ mod tests { use super::{fuzzy_search, *}; use crate::utils::is_email_regex; + use pretty_assertions::assert_eq; #[test] fn test_fuzzy_search() { diff --git a/crates/db_views/Cargo.toml b/crates/db_views/Cargo.toml index 5c8fd21eb..e5bebed36 100644 --- a/crates/db_views/Cargo.toml +++ b/crates/db_views/Cargo.toml @@ -42,3 +42,4 @@ actix-web = { workspace = true, optional = true } serial_test = { workspace = true } tokio = { workspace = true } chrono = { workspace = true } +pretty_assertions = { workspace = true } diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index a26b984b2..83ab5c063 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -234,6 +234,7 @@ mod tests { traits::{Crud, Joinable, Reportable}, utils::{build_db_pool_for_tests, RANK_DEFAULT}, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 4b6db17a5..09984cf81 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -416,6 +416,7 @@ mod tests { utils::{build_db_pool_for_tests, RANK_DEFAULT}, SubscribedType, }; + use pretty_assertions::assert_eq; use serial_test::serial; struct Data { diff --git a/crates/db_views/src/lib.rs b/crates/db_views/src/lib.rs index 8abf776ba..73310d743 100644 --- a/crates/db_views/src/lib.rs +++ b/crates/db_views/src/lib.rs @@ -22,3 +22,5 @@ pub mod registration_application_view; #[cfg(feature = "full")] pub mod site_view; pub mod structs; +#[cfg(feature = "full")] +pub mod vote_view; diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index 843b1ee54..00918970d 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -208,6 +208,7 @@ mod tests { traits::{Crud, Joinable, Reportable}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 5868dbbb0..44677a0f9 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -749,6 +749,7 @@ mod tests { SortType, SubscribedType, }; + use pretty_assertions::{assert_eq, assert_ne}; use serial_test::serial; use std::{collections::HashSet, time::Duration}; diff --git a/crates/db_views/src/private_message_report_view.rs b/crates/db_views/src/private_message_report_view.rs index 134efc05b..712e53eb9 100644 --- a/crates/db_views/src/private_message_report_view.rs +++ b/crates/db_views/src/private_message_report_view.rs @@ -121,6 +121,7 @@ mod tests { traits::{Crud, Reportable}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_views/src/private_message_view.rs b/crates/db_views/src/private_message_view.rs index 26d97038c..6aa87f670 100644 --- a/crates/db_views/src/private_message_view.rs +++ b/crates/db_views/src/private_message_view.rs @@ -168,6 +168,7 @@ mod tests { traits::{Blockable, Crud}, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index 9a298eb77..a59158318 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -149,6 +149,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; #[tokio::test] diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index d0f7fcfc9..a68001a52 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -184,3 +184,14 @@ pub struct CustomEmojiView { pub custom_emoji: CustomEmoji, pub keywords: Vec, } + +#[skip_serializing_none] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] +#[cfg_attr(feature = "full", ts(export))] +/// A vote view for checking a post or comments votes. +pub struct VoteView { + pub creator: Person, + pub score: i16, +} diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs new file mode 100644 index 000000000..723c188f5 --- /dev/null +++ b/crates/db_views/src/vote_view.rs @@ -0,0 +1,196 @@ +use crate::structs::VoteView; +use diesel::{result::Error, ExpressionMethods, QueryDsl}; +use diesel_async::RunQueryDsl; +use lemmy_db_schema::{ + newtypes::{CommentId, PostId}, + schema::{comment_like, person, post_like}, + utils::{get_conn, limit_and_offset, DbPool}, +}; + +impl VoteView { + pub async fn list_for_post( + pool: &mut DbPool<'_>, + post_id: PostId, + page: Option, + limit: Option, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + let (limit, offset) = limit_and_offset(page, limit)?; + + post_like::table + .inner_join(person::table) + .filter(post_like::post_id.eq(post_id)) + .select((person::all_columns, post_like::score)) + .order_by(post_like::score) + .limit(limit) + .offset(offset) + .load::(conn) + .await + } + + pub async fn list_for_comment( + pool: &mut DbPool<'_>, + comment_id: CommentId, + page: Option, + limit: Option, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + let (limit, offset) = limit_and_offset(page, limit)?; + + comment_like::table + .inner_join(person::table) + .filter(comment_like::comment_id.eq(comment_id)) + .select((person::all_columns, comment_like::score)) + .order_by(comment_like::score) + .limit(limit) + .offset(offset) + .load::(conn) + .await + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + #![allow(clippy::indexing_slicing)] + + use crate::structs::VoteView; + use lemmy_db_schema::{ + source::{ + comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm}, + community::{Community, CommunityInsertForm}, + instance::Instance, + person::{Person, PersonInsertForm}, + post::{Post, PostInsertForm, PostLike, PostLikeForm}, + }, + traits::{Crud, Likeable}, + utils::build_db_pool_for_tests, + }; + use pretty_assertions::assert_eq; + use serial_test::serial; + + #[tokio::test] + #[serial] + async fn post_and_comment_vote_views() { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + + let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) + .await + .unwrap(); + + let new_person = PersonInsertForm::builder() + .name("timmy_vv".into()) + .public_key("pubkey".to_string()) + .instance_id(inserted_instance.id) + .build(); + + let inserted_timmy = Person::create(pool, &new_person).await.unwrap(); + + let new_person_2 = PersonInsertForm::builder() + .name("sara_vv".into()) + .public_key("pubkey".to_string()) + .instance_id(inserted_instance.id) + .build(); + + let inserted_sara = Person::create(pool, &new_person_2).await.unwrap(); + + let new_community = CommunityInsertForm::builder() + .name("test community vv".to_string()) + .title("nada".to_owned()) + .public_key("pubkey".to_string()) + .instance_id(inserted_instance.id) + .build(); + + let inserted_community = Community::create(pool, &new_community).await.unwrap(); + + let new_post = PostInsertForm::builder() + .name("A test post vv".into()) + .creator_id(inserted_timmy.id) + .community_id(inserted_community.id) + .build(); + + let inserted_post = Post::create(pool, &new_post).await.unwrap(); + + let comment_form = CommentInsertForm::builder() + .content("A test comment vv".into()) + .creator_id(inserted_timmy.id) + .post_id(inserted_post.id) + .build(); + + let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap(); + + // 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.unwrap(); + + // 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.unwrap(); + + let expected_post_vote_views = [ + VoteView { + creator: inserted_sara.clone(), + score: -1, + }, + VoteView { + creator: inserted_timmy.clone(), + score: 1, + }, + ]; + + let read_post_vote_views = VoteView::list_for_post(pool, inserted_post.id, None, None) + .await + .unwrap(); + assert_eq!(read_post_vote_views, expected_post_vote_views); + + // Timothy votes down his own comment + let timmy_comment_vote_form = CommentLikeForm { + post_id: inserted_post.id, + comment_id: inserted_comment.id, + person_id: inserted_timmy.id, + score: -1, + }; + CommentLike::like(pool, &timmy_comment_vote_form) + .await + .unwrap(); + + // Sara upvotes timmy's comment + let sara_comment_vote_form = CommentLikeForm { + post_id: inserted_post.id, + comment_id: inserted_comment.id, + person_id: inserted_sara.id, + score: 1, + }; + CommentLike::like(pool, &sara_comment_vote_form) + .await + .unwrap(); + + let expected_comment_vote_views = [ + VoteView { + creator: inserted_timmy.clone(), + score: -1, + }, + VoteView { + creator: inserted_sara.clone(), + score: 1, + }, + ]; + + let read_comment_vote_views = VoteView::list_for_comment(pool, inserted_comment.id, None, None) + .await + .unwrap(); + assert_eq!(read_comment_vote_views, expected_comment_vote_views); + + // Cleanup + Instance::delete(pool, inserted_instance.id).await.unwrap(); + } +} diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml index 347b25671..066b6bfd3 100644 --- a/crates/db_views_actor/Cargo.toml +++ b/crates/db_views_actor/Cargo.toml @@ -38,6 +38,7 @@ strum_macros = { workspace = true } [dev-dependencies] serial_test = { workspace = true } tokio = { workspace = true } +pretty_assertions = { workspace = true } [package.metadata.cargo-machete] ignored = ["strum"] diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index 16e9b3bc6..2fff7cc76 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -167,6 +167,7 @@ mod tests { traits::Crud, utils::build_db_pool_for_tests, }; + use pretty_assertions::assert_eq; use serial_test::serial; struct Data { diff --git a/crates/federate/src/lib.rs b/crates/federate/src/lib.rs index 382aa59b8..a4dc49536 100644 --- a/crates/federate/src/lib.rs +++ b/crates/federate/src/lib.rs @@ -23,6 +23,7 @@ static INSTANCES_RECHECK_DELAY: Duration = Duration::from_secs(5); #[cfg(not(debug_assertions))] static INSTANCES_RECHECK_DELAY: Duration = Duration::from_secs(60); +#[derive(Clone)] pub struct Opts { /// how many processes you are starting in total pub process_count: i32, @@ -36,7 +37,7 @@ async fn start_stop_federation_workers( federation_config: FederationConfig, cancel: CancellationToken, ) -> anyhow::Result<()> { - let mut workers = HashMap::>::new(); + let mut workers = HashMap::::new(); let (stats_sender, stats_receiver) = unbounded_channel(); let exit_print = tokio::spawn(receive_print_stats(pool.clone(), stats_receiver)); @@ -66,40 +67,30 @@ async fn start_stop_federation_workers( let should_federate = allowed && !is_dead; if should_federate { if workers.contains_key(&instance.id) { - if workers - .get(&instance.id) - .map(util::CancellableTask::has_ended) - .unwrap_or(false) - { - // task must have errored out, remove and recreated it - let worker = workers - .remove(&instance.id) - .expect("just checked contains_key"); - tracing::error!( - "worker for {} has stopped, recreating: {:?}", - instance.domain, - worker.cancel().await - ); - } else { - continue; - } + // worker already running + continue; } // create new worker + let config = federation_config.clone(); let stats_sender = stats_sender.clone(); - let context = federation_config.to_request_data(); let pool = pool.clone(); workers.insert( instance.id, - CancellableTask::spawn(WORKER_EXIT_TIMEOUT, |stop| async move { - InstanceWorker::init_and_loop( - instance, - context, - &mut DbPool::Pool(&pool), - stop, - stats_sender, - ) - .await?; - Ok(()) + CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |stop| { + let instance = instance.clone(); + let req_data = config.clone().to_request_data(); + let stats_sender = stats_sender.clone(); + let pool = pool.clone(); + async move { + InstanceWorker::init_and_loop( + instance, + req_data, + &mut DbPool::Pool(&pool), + stop, + stats_sender, + ) + .await + } }), ); } else if !should_federate { @@ -135,9 +126,12 @@ pub fn start_stop_federation_workers_cancellable( opts: Opts, pool: ActualDbPool, config: FederationConfig, -) -> CancellableTask<()> { - CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |c| { - start_stop_federation_workers(opts, pool, config, c) +) -> CancellableTask { + CancellableTask::spawn(WORKER_EXIT_TIMEOUT, move |stop| { + let opts = opts.clone(); + let pool = pool.clone(); + let config = config.clone(); + async move { start_stop_federation_workers(opts, pool, config, stop).await } }) } diff --git a/crates/federate/src/util.rs b/crates/federate/src/util.rs index 1775e4153..848785836 100644 --- a/crates/federate/src/util.rs +++ b/crates/federate/src/util.rs @@ -20,12 +20,7 @@ use moka::future::Cache; use once_cell::sync::Lazy; use reqwest::Url; use serde_json::Value; -use std::{ - future::Future, - pin::Pin, - sync::{Arc, RwLock}, - time::Duration, -}; +use std::{fmt::Debug, future::Future, pin::Pin, sync::Arc, time::Duration}; use tokio::{task::JoinHandle, time::sleep}; use tokio_util::sync::CancellationToken; @@ -49,41 +44,41 @@ pub(crate) static WORK_FINISHED_RECHECK_DELAY: Lazy = Lazy::new(|| { } }); -pub struct CancellableTask { - f: Pin> + Send + 'static>>, - ended: Arc>, +/// A task that will be run in an infinite loop, unless it is cancelled. +/// If the task exits without being cancelled, an error will be logged and the task will be restarted. +pub struct CancellableTask { + f: Pin> + Send + 'static>>, } -impl CancellableTask { +impl CancellableTask { /// spawn a task but with graceful shutdown - pub fn spawn( + pub fn spawn( timeout: Duration, - task: impl FnOnce(CancellationToken) -> F, - ) -> CancellableTask + task: impl Fn(CancellationToken) -> F + Send + 'static, + ) -> CancellableTask where - F: Future> + Send + 'static, + F: Future + Send + 'static, { let stop = CancellationToken::new(); - let task = task(stop.clone()); - let ended = Arc::new(RwLock::new(false)); - let ended_write = ended.clone(); - let task: JoinHandle> = tokio::spawn(async move { - match task.await { - Ok(o) => Ok(o), - Err(e) => { - *ended_write.write().expect("poisoned") = true; - Err(e) + let stop2 = stop.clone(); + let task: JoinHandle<()> = tokio::spawn(async move { + loop { + let res = task(stop2.clone()).await; + if stop2.is_cancelled() { + return; + } else { + tracing::warn!("task exited, restarting: {res:?}"); } } }); let abort = task.abort_handle(); CancellableTask { - ended, f: Box::pin(async move { stop.cancel(); tokio::select! { r = task => { - Ok(r.context("could not join")??) + r.context("could not join")?; + Ok(()) }, _ = sleep(timeout) => { abort.abort(); @@ -96,12 +91,9 @@ impl CancellableTask { } /// cancel the cancel signal, wait for timeout for the task to stop gracefully, otherwise abort it - pub async fn cancel(self) -> Result { + pub async fn cancel(self) -> Result<(), anyhow::Error> { self.f.await } - pub fn has_ended(&self) -> bool { - *self.ended.read().expect("poisoned") - } } /// assuming apub priv key and ids are immutable, then we don't need to have TTL diff --git a/crates/federate/src/worker.rs b/crates/federate/src/worker.rs index 963814ad9..6dff325b6 100644 --- a/crates/federate/src/worker.rs +++ b/crates/federate/src/worker.rs @@ -206,6 +206,7 @@ impl InstanceWorker { .await .context("failed figuring out inbox urls")?; if inbox_urls.is_empty() { + tracing::debug!("{}: {:?} no inboxes", self.instance.domain, activity.id); self.state.last_successful_id = Some(activity.id); self.state.last_successful_published_time = Some(activity.published); return Ok(()); diff --git a/crates/routes/src/lib.rs b/crates/routes/src/lib.rs index ec28fda45..b1e97cd3c 100644 --- a/crates/routes/src/lib.rs +++ b/crates/routes/src/lib.rs @@ -1,6 +1,6 @@ use lemmy_api_common::{claims::Claims, context::LemmyContext, utils::check_user_valid}; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; pub mod feeds; pub mod images; @@ -12,7 +12,9 @@ async fn local_user_view_from_jwt( jwt: &str, context: &LemmyContext, ) -> Result { - let local_user_id = Claims::validate(jwt, context).await?; + let local_user_id = Claims::validate(jwt, context) + .await + .with_lemmy_type(LemmyErrorType::NotLoggedIn)?; let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?; check_user_valid(&local_user_view.person)?; diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 57fd10655..7e61cb595 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -52,6 +52,7 @@ enum-map = { workspace = true } [dev-dependencies] reqwest = { workspace = true } +pretty_assertions = { workspace = true } [build-dependencies] rosetta-build = { version = "0.1.3", default-features = false } diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index 7d84dcbf6..2d19e85b5 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -279,6 +279,7 @@ mod tests { #![allow(clippy::indexing_slicing)] use super::*; use actix_web::{body::MessageBody, ResponseError}; + use pretty_assertions::assert_eq; use std::fs::read_to_string; use strum::IntoEnumIterator; diff --git a/crates/utils/src/rate_limit/rate_limiter.rs b/crates/utils/src/rate_limit/rate_limiter.rs index d0dad5df2..93ef1beec 100644 --- a/crates/utils/src/rate_limit/rate_limiter.rs +++ b/crates/utils/src/rate_limit/rate_limiter.rs @@ -310,6 +310,7 @@ mod tests { #![allow(clippy::indexing_slicing)] use super::{ActionType, BucketConfig, InstantSecs, RateLimitState, RateLimitedGroup}; + use pretty_assertions::assert_eq; #[test] fn test_split_ipv6() { diff --git a/crates/utils/src/response.rs b/crates/utils/src/response.rs index b521c0963..82b1e70ed 100644 --- a/crates/utils/src/response.rs +++ b/crates/utils/src/response.rs @@ -46,6 +46,7 @@ mod tests { Responder, }; use http::StatusCode; + use pretty_assertions::assert_eq; #[actix_web::test] async fn test_non_error_responses_are_not_modified() { diff --git a/crates/utils/src/utils/markdown/mod.rs b/crates/utils/src/utils/markdown/mod.rs index 7c9580c47..bee2dcb94 100644 --- a/crates/utils/src/utils/markdown/mod.rs +++ b/crates/utils/src/utils/markdown/mod.rs @@ -104,6 +104,7 @@ mod tests { #![allow(clippy::indexing_slicing)] use super::*; + use pretty_assertions::assert_eq; #[test] fn test_basic_markdown() { diff --git a/crates/utils/src/utils/markdown/spoiler_rule.rs b/crates/utils/src/utils/markdown/spoiler_rule.rs index bae858bfd..e41ea436f 100644 --- a/crates/utils/src/utils/markdown/spoiler_rule.rs +++ b/crates/utils/src/utils/markdown/spoiler_rule.rs @@ -140,6 +140,7 @@ mod tests { use crate::utils::markdown::spoiler_rule::add; use markdown_it::MarkdownIt; + use pretty_assertions::assert_eq; #[test] fn test_spoiler_markdown() { diff --git a/crates/utils/src/utils/mention.rs b/crates/utils/src/utils/mention.rs index a2958e499..9e9ee64a2 100644 --- a/crates/utils/src/utils/mention.rs +++ b/crates/utils/src/utils/mention.rs @@ -39,6 +39,7 @@ mod test { #![allow(clippy::indexing_slicing)] use crate::utils::mention::scrape_text_for_mentions; + use pretty_assertions::assert_eq; #[test] fn test_mentions_regex() { diff --git a/crates/utils/src/utils/slurs.rs b/crates/utils/src/utils/slurs.rs index cc2d6a3e6..fba43d706 100644 --- a/crates/utils/src/utils/slurs.rs +++ b/crates/utils/src/utils/slurs.rs @@ -69,6 +69,7 @@ mod test { #![allow(clippy::indexing_slicing)] use crate::utils::slurs::{remove_slurs, slur_check, slurs_vec_to_str}; + use pretty_assertions::assert_eq; use regex::RegexBuilder; #[test] diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index 654232a46..bb4592fc5 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -6,7 +6,7 @@ use url::Url; // From here: https://github.com/vector-im/element-android/blob/develop/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt#L35 static VALID_MATRIX_ID_REGEX: Lazy = Lazy::new(|| { - Regex::new(r"^@[A-Za-z0-9\\x21-\\x39\\x3B-\\x7F]+:[A-Za-z0-9.-]+(:[0-9]{2,5})?$") + Regex::new(r"^@[A-Za-z0-9\x21-\x39\x3B-\x7F]+:[A-Za-z0-9.-]+(:[0-9]{2,5})?$") .expect("compile regex") }); // taken from https://en.wikipedia.org/wiki/UTM_parameters @@ -310,6 +310,7 @@ mod tests { SITE_NAME_MAX_LENGTH, }, }; + use pretty_assertions::assert_eq; use url::Url; #[test] @@ -386,6 +387,7 @@ mod tests { #[test] fn test_valid_matrix_id() { assert!(is_valid_matrix_id("@dess:matrix.org").is_ok()); + assert!(is_valid_matrix_id("@dess_:matrix.org").is_ok()); assert!(is_valid_matrix_id("@dess:matrix.org:443").is_ok()); assert!(is_valid_matrix_id("dess:matrix.org").is_err()); assert!(is_valid_matrix_id(" @dess:matrix.org").is_err()); diff --git a/migrations/2024-01-02-094916_site-name-not-unique/down.sql b/migrations/2024-01-02-094916_site-name-not-unique/down.sql new file mode 100644 index 000000000..eef68d2fe --- /dev/null +++ b/migrations/2024-01-02-094916_site-name-not-unique/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE site + ADD CONSTRAINT site_name_key UNIQUE (name); + diff --git a/migrations/2024-01-02-094916_site-name-not-unique/up.sql b/migrations/2024-01-02-094916_site-name-not-unique/up.sql new file mode 100644 index 000000000..02f07d67a --- /dev/null +++ b/migrations/2024-01-02-094916_site-name-not-unique/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE site + DROP CONSTRAINT site_name_key; + diff --git a/readmes/README.es.md b/readmes/README.es.md index b53b2b020..56fc908b2 100644 --- a/readmes/README.es.md +++ b/readmes/README.es.md @@ -45,9 +45,9 @@ ## Sobre El Proyecto -| Escritorio | Móvil | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| Escritorio | Móvil | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) es similar a sitios como [Menéame](https://www.meneame.net/), [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), o [Hacker News](https://news.ycombinator.com/): te subscribes a los foros que te interesan, publicas enlaces y debates, luego votas y comentas en ellos. Entre bastidores, es muy diferente; cualquiera puede gestionar fácilmente un servidor, y todos estos servidores son federados (piensa en el correo electrónico), y conectados al mismo universo, llamado [Fediverso](https://es.wikipedia.org/wiki/Fediverso). diff --git a/readmes/README.ja.md b/readmes/README.ja.md index e026a9091..0a64cd055 100644 --- a/readmes/README.ja.md +++ b/readmes/README.ja.md @@ -47,9 +47,9 @@ ## プロジェクトについて -| デスクトップ | モバイル | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| デスクトップ | モバイル | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) は、[Reddit](https://reddit.com)、[Lobste.rs](https://lobste.rs)、[Hacker News](https://news.ycombinator.com/) といったサイトに似ています。興味のあるフォーラムを購読してリンクや議論を掲載し、投票したり、コメントしたりしています。誰でも簡単にサーバーを運営することができ、これらのサーバーはすべて連合しており(電子メールを考えてください)、[Fediverse](https://en.wikipedia.org/wiki/Fediverse) と呼ばれる同じ宇宙に接続されています。 diff --git a/readmes/README.ru.md b/readmes/README.ru.md index d9693901d..1eb585e69 100644 --- a/readmes/README.ru.md +++ b/readmes/README.ru.md @@ -45,9 +45,9 @@ ## О проекте -| Десктоп | Мобильный | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| Десктоп | Мобильный | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) это аналог таких сайтов как [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), или [Hacker News](https://news.ycombinator.com/): вы подписываетесь на форумы, которые вас интересуют , размещаете ссылки и дискутируете, затем голосуете и комментируете их. Однако за кулисами всё совсем по-другому; любой может легко запустить сервер, и все эти серверы объединены (например электронная почта) и подключены к одной вселенной, именуемой [Федиверс](https://ru.wikipedia.org/wiki/Fediverse). diff --git a/readmes/README.zh.hans.md b/readmes/README.zh.hans.md index 3c21e1fa4..56ab1111d 100644 --- a/readmes/README.zh.hans.md +++ b/readmes/README.zh.hans.md @@ -47,9 +47,9 @@ ## 关于项目 -| 桌面应用 | 移动应用 | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| 桌面应用 | 移动应用 | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) 与 [Reddit](https://reddit.com)、[Lobste.rs](https://lobste.rs) 或 [Hacker News](https://news.ycombinator.com/) 等网站类似:你可以订阅你感兴趣的论坛,发布链接和讨论,然后进行投票或评论。但在幕后,Lemmy 和他们不同——任何人都可以很容易地运行一个服务器,所有服务器都是联邦式的(想想电子邮件),并连接到 [联邦宇宙](https://zh.wikipedia.org/wiki/%E8%81%94%E9%82%A6%E5%AE%87%E5%AE%99)。 diff --git a/readmes/README.zh.hant.md b/readmes/README.zh.hant.md index aa2c0ff7b..3b08e0cf1 100644 --- a/readmes/README.zh.hant.md +++ b/readmes/README.zh.hant.md @@ -48,9 +48,9 @@ ## 關於專案 -| 桌面設備 | 行動裝置 | -| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_img.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | +| 桌面設備 | 行動裝置 | +| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| ![desktop](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/main_screen_2.webp) | ![mobile](https://raw.githubusercontent.com/LemmyNet/joinlemmy-site/main/src/assets/images/mobile_pic.webp) | [Lemmy](https://github.com/LemmyNet/lemmy) 與 [Reddit](https://reddit.com)、[Lobste.rs](https://lobste.rs) 或 [Hacker News](https://news.ycombinator.com/) 等網站類似:你可以訂閱你感興趣的論壇,釋出連結和討論,然後進行投票或評論。但在幕後,Lemmy 和他們不同——任何人都可以很容易地架設一個伺服器,所有伺服器都是聯邦式的(想想電子郵件),並與 [聯邦宇宙](https://zh.wikipedia.org/wiki/%E8%81%94%E9%82%A6%E5%AE%87%E5%AE%99) 互聯。 diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index f6586928d..912dcfbf9 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -1,6 +1,11 @@ use actix_web::{guard, web}; use lemmy_api::{ - comment::{distinguish::distinguish_comment, like::like_comment, save::save_comment}, + comment::{ + distinguish::distinguish_comment, + like::like_comment, + list_comment_likes::list_comment_likes, + save::save_comment, + }, comment_report::{ create::create_comment_report, list::list_comment_reports, @@ -45,6 +50,7 @@ use lemmy_api::{ feature::feature_post, get_link_metadata::get_link_metadata, like::like_post, + list_post_likes::list_post_likes, lock::lock_post, mark_read::mark_post_as_read, save::save_post, @@ -204,6 +210,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/feature", web::post().to(feature_post)) .route("/list", web::get().to(list_posts)) .route("/like", web::post().to(like_post)) + .route("/like/list", web::get().to(list_post_likes)) .route("/save", web::put().to(save_post)) .route("/report", web::post().to(create_post_report)) .route("/report/resolve", web::put().to(resolve_post_report)) @@ -228,6 +235,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/mark_as_read", web::post().to(mark_reply_as_read)) .route("/distinguish", web::post().to(distinguish_comment)) .route("/like", web::post().to(like_comment)) + .route("/like/list", web::get().to(list_comment_likes)) .route("/save", web::put().to(save_comment)) .route("/list", web::get().to(list_comments)) .route("/report", web::post().to(create_comment_report)) diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index b669836e5..05ff4b9e2 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -516,6 +516,7 @@ mod tests { #![allow(clippy::indexing_slicing)] use lemmy_routes::nodeinfo::NodeInfo; + use pretty_assertions::assert_eq; use reqwest::Client; #[tokio::test] diff --git a/src/session_middleware.rs b/src/session_middleware.rs index f50e0eccd..d81251453 100644 --- a/src/session_middleware.rs +++ b/src/session_middleware.rs @@ -7,8 +7,14 @@ use actix_web::{ }; use core::future::Ready; use futures_util::future::LocalBoxFuture; -use lemmy_api::{local_user_view_from_jwt, read_auth_token}; -use lemmy_api_common::context::LemmyContext; +use lemmy_api::read_auth_token; +use lemmy_api_common::{ + claims::Claims, + context::LemmyContext, + lemmy_db_views::structs::LocalUserView, + utils::check_user_valid, +}; +use lemmy_utils::error::{LemmyError, LemmyErrorExt2, LemmyErrorType}; use reqwest::header::HeaderValue; use std::{future::ready, rc::Rc}; @@ -67,14 +73,14 @@ where let jwt = read_auth_token(req.request())?; if let Some(jwt) = &jwt { - // Ignore any invalid auth so the site can still be used - // TODO: this means it will be impossible to get any error message for invalid jwt. Need - // to add a separate endpoint for that. - // https://github.com/LemmyNet/lemmy/issues/3702 - let local_user_view = local_user_view_from_jwt(jwt, &context).await.ok(); - if let Some(local_user_view) = local_user_view { - req.extensions_mut().insert(local_user_view); - } + let local_user_id = Claims::validate(jwt, &context) + .await + .with_lemmy_type(LemmyErrorType::IncorrectLogin)?; + let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id) + .await + .map_err(LemmyError::from)?; + check_user_valid(&local_user_view.person)?; + req.extensions_mut().insert(local_user_view); } let mut res = svc.call(req).await?; @@ -113,6 +119,7 @@ mod tests { utils::build_db_pool_for_tests, }; use lemmy_utils::rate_limit::RateLimitCell; + use pretty_assertions::assert_eq; use reqwest::Client; use reqwest_middleware::ClientBuilder; use serial_test::serial;