diff --git a/api_tests/package.json b/api_tests/package.json index b06b84fbf..f28fd7689 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -27,7 +27,7 @@ "eslint": "^9.9.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.5.0", - "lemmy-js-client": "0.20.0-alpha.11", + "lemmy-js-client": "0.20.0-alpha.12", "prettier": "^3.2.5", "ts-jest": "^29.1.0", "typescript": "^5.5.4", diff --git a/api_tests/pnpm-lock.yaml b/api_tests/pnpm-lock.yaml index 54c8a9d96..f3895a666 100644 --- a/api_tests/pnpm-lock.yaml +++ b/api_tests/pnpm-lock.yaml @@ -30,8 +30,8 @@ importers: specifier: ^29.5.0 version: 29.7.0(@types/node@22.3.0) lemmy-js-client: - specifier: 0.20.0-alpha.11 - version: 0.20.0-alpha.11 + specifier: 0.20.0-alpha.12 + version: 0.20.0-alpha.12 prettier: specifier: ^3.2.5 version: 3.3.3 @@ -1175,8 +1175,8 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - lemmy-js-client@0.20.0-alpha.11: - resolution: {integrity: sha512-iRSG4xHMjPDIreQqVIoJ5JrMY71uk07G0Zbgyf068xKbib22J3+i1x/XgCTs6tiHlqTnw1Ig/KRq7p7qJoA4uw==} + lemmy-js-client@0.20.0-alpha.12: + resolution: {integrity: sha512-+nknIpFAT25TnhObvPPCI0JhDxmTSfg3jbNm7f4RnwidRskjS8SraNFD4bXFfHf14lu61ZEiVe58+UhhRe2UdA==} leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} @@ -3107,7 +3107,7 @@ snapshots: kleur@3.0.3: {} - lemmy-js-client@0.20.0-alpha.11: {} + lemmy-js-client@0.20.0-alpha.12: {} leven@3.1.0: {} diff --git a/crates/api/src/post/hide.rs b/crates/api/src/post/hide.rs index f7c21ef31..58464421c 100644 --- a/crates/api/src/post/hide.rs +++ b/crates/api/src/post/hide.rs @@ -1,34 +1,39 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{context::LemmyContext, post::HidePost, SuccessResponse}; +use lemmy_api_common::{ + context::LemmyContext, + post::{HidePost, PostResponse}, +}; use lemmy_db_schema::source::post::PostHide; -use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS}; -use std::collections::HashSet; +use lemmy_db_views::structs::{LocalUserView, PostView}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn hide_post( data: Json, context: Data, local_user_view: LocalUserView, -) -> LemmyResult> { - let post_ids = HashSet::from_iter(data.post_ids.clone()); - - if post_ids.len() > MAX_API_PARAM_ELEMENTS { - Err(LemmyErrorType::TooManyItems)?; - } - +) -> LemmyResult> { let person_id = local_user_view.person.id; + let post_id = data.post_id; // Mark the post as hidden / unhidden if data.hide { - PostHide::hide(&mut context.pool(), post_ids, person_id) + PostHide::hide(&mut context.pool(), post_id, person_id) .await .with_lemmy_type(LemmyErrorType::CouldntHidePost)?; } else { - PostHide::unhide(&mut context.pool(), post_ids, person_id) + PostHide::unhide(&mut context.pool(), post_id, person_id) .await .with_lemmy_type(LemmyErrorType::CouldntHidePost)?; } - Ok(Json(SuccessResponse::default())) + let post_view = PostView::read( + &mut context.pool(), + post_id, + Some(&local_user_view.local_user), + false, + ) + .await?; + + Ok(Json(PostResponse { post_view })) } diff --git a/crates/api/src/post/mark_read.rs b/crates/api/src/post/mark_read.rs index 3e534675a..893be56b6 100644 --- a/crates/api/src/post/mark_read.rs +++ b/crates/api/src/post/mark_read.rs @@ -1,34 +1,38 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{context::LemmyContext, post::MarkPostAsRead, SuccessResponse}; +use lemmy_api_common::{ + context::LemmyContext, + post::{MarkPostAsRead, PostResponse}, +}; use lemmy_db_schema::source::post::PostRead; -use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS}; -use std::collections::HashSet; +use lemmy_db_views::structs::{LocalUserView, PostView}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] pub async fn mark_post_as_read( data: Json, context: Data, local_user_view: LocalUserView, -) -> LemmyResult> { - let post_ids = HashSet::from_iter(data.post_ids.clone()); - - if post_ids.len() > MAX_API_PARAM_ELEMENTS { - Err(LemmyErrorType::TooManyItems)?; - } - +) -> LemmyResult> { let person_id = local_user_view.person.id; + let post_id = data.post_id; // Mark the post as read / unread if data.read { - PostRead::mark_as_read(&mut context.pool(), post_ids, person_id) + PostRead::mark_as_read(&mut context.pool(), post_id, person_id) .await .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; } else { - PostRead::mark_as_unread(&mut context.pool(), post_ids, person_id) + PostRead::mark_as_unread(&mut context.pool(), post_id, person_id) .await .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; } + let post_view = PostView::read( + &mut context.pool(), + post_id, + Some(&local_user_view.local_user), + false, + ) + .await?; - Ok(Json(SuccessResponse::default())) + Ok(Json(PostResponse { post_view })) } diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index fa45459e2..944d2d5da 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -156,7 +156,7 @@ pub struct RemovePost { #[cfg_attr(feature = "full", ts(export))] /// Mark a post as read. pub struct MarkPostAsRead { - pub post_ids: Vec, + pub post_id: PostId, pub read: bool, } @@ -166,7 +166,7 @@ pub struct MarkPostAsRead { #[cfg_attr(feature = "full", ts(export))] /// Hide a post from list views pub struct HidePost { - pub post_ids: Vec, + pub post_id: PostId, pub hide: bool, } diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index b83b9b582..682b61063 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -59,7 +59,7 @@ use lemmy_utils::{ use moka::future::Cache; use regex::{escape, Regex, RegexSet}; use rosetta_i18n::{Language, LanguageId}; -use std::{collections::HashSet, sync::LazyLock}; +use std::sync::LazyLock; use tracing::warn; use url::{ParseError, Url}; use urlencoding::encode; @@ -142,7 +142,7 @@ pub async fn mark_post_as_read( post_id: PostId, pool: &mut DbPool<'_>, ) -> LemmyResult<()> { - PostRead::mark_as_read(pool, HashSet::from([post_id]), person_id) + PostRead::mark_as_read(pool, post_id, person_id) .await .with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?; Ok(()) diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index aa5568b0e..ab8bfdc29 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -39,7 +39,6 @@ use diesel::{ TextExpressionMethods, }; use diesel_async::RunQueryDsl; -use std::collections::HashSet; #[async_trait] impl Crud for Post { @@ -322,17 +321,15 @@ impl Saveable for PostSaved { impl PostRead { pub async fn mark_as_read( pool: &mut DbPool<'_>, - post_ids: HashSet, + post_id: PostId, person_id: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - let forms = post_ids - .into_iter() - .map(|post_id| PostReadForm { post_id, person_id }) - .collect::>(); + let form = PostReadForm { post_id, person_id }; + insert_into(post_read::table) - .values(forms) + .values(form) .on_conflict_do_nothing() .execute(conn) .await @@ -340,35 +337,30 @@ impl PostRead { pub async fn mark_as_unread( pool: &mut DbPool<'_>, - post_id_: HashSet, + post_id_: PostId, person_id_: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::delete( - post_read::table - .filter(post_read::post_id.eq_any(post_id_)) - .filter(post_read::person_id.eq(person_id_)), - ) - .execute(conn) - .await + let read_post = post_read::table + .filter(post_read::post_id.eq(post_id_)) + .filter(post_read::person_id.eq(person_id_)); + + diesel::delete(read_post).execute(conn).await } } impl PostHide { pub async fn hide( pool: &mut DbPool<'_>, - post_ids: HashSet, + post_id: PostId, person_id: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - let forms = post_ids - .into_iter() - .map(|post_id| PostHideForm { post_id, person_id }) - .collect::>(); + let form = PostHideForm { post_id, person_id }; insert_into(post_hide::table) - .values(forms) + .values(form) .on_conflict_do_nothing() .execute(conn) .await @@ -376,23 +368,21 @@ impl PostHide { pub async fn unhide( pool: &mut DbPool<'_>, - post_id_: HashSet, + post_id_: PostId, person_id_: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::delete( - post_hide::table - .filter(post_hide::post_id.eq_any(post_id_)) - .filter(post_hide::person_id.eq(person_id_)), - ) - .execute(conn) - .await + let hidden_post = post_hide::table + .filter(post_hide::post_id.eq(post_id_)) + .filter(post_hide::person_id.eq(person_id_)); + + diesel::delete(hidden_post).execute(conn).await } } #[cfg(test)] -#[expect(clippy::unwrap_used)] +#[allow(clippy::indexing_slicing)] mod tests { use crate::{ @@ -415,24 +405,22 @@ mod tests { utils::build_db_pool_for_tests, }; use chrono::DateTime; + use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; - use std::collections::HashSet; use url::Url; #[tokio::test] #[serial] - async fn test_crud() { + async fn test_crud() -> LemmyResult<()> { 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 inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let new_person = PersonInsertForm::test_form(inserted_instance.id, "jim"); - let inserted_person = Person::create(pool, &new_person).await.unwrap(); + let inserted_person = Person::create(pool, &new_person).await?; let new_community = CommunityInsertForm::new( inserted_instance.id, @@ -441,27 +429,27 @@ mod tests { "pubkey".to_string(), ); - let inserted_community = Community::create(pool, &new_community).await.unwrap(); + let inserted_community = Community::create(pool, &new_community).await?; let new_post = PostInsertForm::new( "A test post".into(), inserted_person.id, inserted_community.id, ); - let inserted_post = Post::create(pool, &new_post).await.unwrap(); + let inserted_post = Post::create(pool, &new_post).await?; let new_post2 = PostInsertForm::new( "A test post 2".into(), inserted_person.id, inserted_community.id, ); - let inserted_post2 = Post::create(pool, &new_post2).await.unwrap(); + let inserted_post2 = Post::create(pool, &new_post2).await?; let new_scheduled_post = PostInsertForm { scheduled_publish_time: Some(DateTime::from_timestamp_nanos(i64::MAX)), ..PostInsertForm::new("beans".into(), inserted_person.id, inserted_community.id) }; - let inserted_scheduled_post = Post::create(pool, &new_scheduled_post).await.unwrap(); + let inserted_scheduled_post = Post::create(pool, &new_scheduled_post).await?; let expected_post = Post { id: inserted_post.id, @@ -481,9 +469,7 @@ mod tests { embed_description: None, embed_video_url: None, thumbnail_url: None, - ap_id: Url::parse(&format!("https://lemmy-alpha/post/{}", inserted_post.id)) - .unwrap() - .into(), + ap_id: Url::parse(&format!("https://lemmy-alpha/post/{}", inserted_post.id))?.into(), local: true, language_id: Default::default(), featured_community: false, @@ -499,7 +485,7 @@ mod tests { score: 1, }; - let inserted_post_like = PostLike::like(pool, &post_like_form).await.unwrap(); + let inserted_post_like = PostLike::like(pool, &post_like_form).await?; let expected_post_like = PostLike { post_id: inserted_post.id, @@ -514,7 +500,7 @@ mod tests { person_id: inserted_person.id, }; - let inserted_post_saved = PostSaved::save(pool, &post_saved_form).await.unwrap(); + let inserted_post_saved = PostSaved::save(pool, &post_saved_form).await?; let expected_post_saved = PostSaved { post_id: inserted_post.id, @@ -522,63 +508,51 @@ mod tests { published: inserted_post_saved.published, }; - // Post Read - let marked_as_read = PostRead::mark_as_read( - pool, - HashSet::from([inserted_post.id, inserted_post2.id]), - inserted_person.id, - ) - .await - .unwrap(); - assert_eq!(2, marked_as_read); + // Mark 2 posts as read + PostRead::mark_as_read(pool, inserted_post.id, inserted_person.id).await?; + PostRead::mark_as_read(pool, inserted_post2.id, inserted_person.id).await?; - let read_post = Post::read(pool, inserted_post.id).await.unwrap(); + let read_post = Post::read(pool, inserted_post.id).await?; let new_post_update = PostUpdateForm { name: Some("A test post".into()), ..Default::default() }; - let updated_post = Post::update(pool, inserted_post.id, &new_post_update) - .await - .unwrap(); + let updated_post = Post::update(pool, inserted_post.id, &new_post_update).await?; // Scheduled post count - let scheduled_post_count = Post::user_scheduled_post_count(inserted_person.id, pool) - .await - .unwrap(); + let scheduled_post_count = Post::user_scheduled_post_count(inserted_person.id, pool).await?; assert_eq!(1, scheduled_post_count); - let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id) - .await - .unwrap(); + let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id).await?; assert_eq!(1, like_removed); - let saved_removed = PostSaved::unsave(pool, &post_saved_form).await.unwrap(); + let saved_removed = PostSaved::unsave(pool, &post_saved_form).await?; assert_eq!(1, saved_removed); - let read_removed = PostRead::mark_as_unread( - pool, - HashSet::from([inserted_post.id, inserted_post2.id]), - inserted_person.id, - ) - .await - .unwrap(); - assert_eq!(2, read_removed); - let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap() - + Post::delete(pool, inserted_post2.id).await.unwrap() - + Post::delete(pool, inserted_scheduled_post.id) - .await - .unwrap(); + // mark some posts as unread + let read_removed_1 = + PostRead::mark_as_unread(pool, inserted_post.id, inserted_person.id).await?; + assert_eq!(1, read_removed_1); + let read_removed_2 = + PostRead::mark_as_unread(pool, inserted_post2.id, inserted_person.id).await?; + assert_eq!(1, read_removed_2); + + let num_deleted = Post::delete(pool, inserted_post.id).await? + + Post::delete(pool, inserted_post2.id).await? + + Post::delete(pool, inserted_scheduled_post.id).await?; + assert_eq!(3, num_deleted); - Community::delete(pool, inserted_community.id) - .await - .unwrap(); - Person::delete(pool, inserted_person.id).await.unwrap(); - Instance::delete(pool, inserted_instance.id).await.unwrap(); + Community::delete(pool, inserted_community.id).await?; + + Person::delete(pool, inserted_person.id).await?; + Instance::delete(pool, inserted_instance.id).await?; assert_eq!(expected_post, read_post); assert_eq!(expected_post, inserted_post); assert_eq!(expected_post, updated_post); assert_eq!(expected_post_like, inserted_post_like); assert_eq!(expected_post_saved, inserted_post_saved); + + Ok(()) } } diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 9ab9ad776..c8066b072 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -792,7 +792,7 @@ mod tests { use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; - use std::{collections::HashSet, time::Duration}; + use std::time::Duration; use url::Url; const POST_WITH_ANOTHER_TITLE: &str = "Another title"; @@ -1627,7 +1627,7 @@ mod tests { // Mark a post as read PostRead::mark_as_read( pool, - HashSet::from([data.inserted_bot_post.id]), + data.inserted_bot_post.id, data.local_user_view.person.id, ) .await?; @@ -1669,7 +1669,7 @@ mod tests { // Mark a post as hidden PostHide::hide( pool, - HashSet::from([data.inserted_bot_post.id]), + data.inserted_bot_post.id, data.local_user_view.person.id, ) .await?;