X-Git-Url: http://these/git/?a=blobdiff_plain;f=crates%2Fdb_schema%2Fsrc%2Fimpls%2Factor_language.rs;h=313762a72f97c070229989d2f3e5711a23d3060d;hb=92568956353f21649ed9aff68b42699c9d036f30;hp=bf3e7ac987e1df1e21a14fe30f757a6dbc35cfc1;hpb=d9e7f0100ad14121b43b89d54b80fb2345d8d83e;p=lemmy.git diff --git a/crates/db_schema/src/impls/actor_language.rs b/crates/db_schema/src/impls/actor_language.rs index bf3e7ac9..313762a7 100644 --- a/crates/db_schema/src/impls/actor_language.rs +++ b/crates/db_schema/src/impls/actor_language.rs @@ -26,14 +26,14 @@ use diesel::{ QueryDsl, }; use diesel_async::{AsyncPgConnection, RunQueryDsl}; -use lemmy_utils::error::LemmyError; +use lemmy_utils::error::{LemmyError, LemmyErrorType}; use tokio::sync::OnceCell; pub const UNDETERMINED_ID: LanguageId = LanguageId(0); impl LocalUserLanguage { pub async fn read( - pool: &DbPool, + pool: &mut DbPool<'_>, for_local_user_id: LocalUserId, ) -> Result, Error> { use crate::schema::local_user_language::dsl::{ @@ -63,11 +63,28 @@ impl LocalUserLanguage { /// /// If no language_id vector is given, it will show all languages pub async fn update( - pool: &DbPool, + pool: &mut DbPool<'_>, language_ids: Vec, for_local_user_id: LocalUserId, ) -> Result<(), Error> { let conn = &mut get_conn(pool).await?; + let mut lang_ids = convert_update_languages(conn, language_ids).await?; + + // No need to update if languages are unchanged + let current = LocalUserLanguage::read(&mut conn.into(), for_local_user_id).await?; + if current == lang_ids { + return Ok(()); + } + + // TODO: Force enable undetermined language for all users. This is necessary because many posts + // don't have a language tag (e.g. those from other federated platforms), so Lemmy users + // won't see them if undetermined language is disabled. + // This hack can be removed once a majority of posts have language tags, or when it is + // clearer for new users that they need to enable undetermined language. + // See https://github.com/LemmyNet/lemmy-ui/issues/999 + if !lang_ids.contains(&UNDETERMINED_ID) { + lang_ids.push(UNDETERMINED_ID); + } conn .build_transaction() @@ -79,7 +96,6 @@ impl LocalUserLanguage { .execute(conn) .await?; - let lang_ids = convert_update_languages(conn, language_ids).await?; for l in lang_ids { let form = LocalUserLanguageForm { local_user_id: for_local_user_id, @@ -98,7 +114,7 @@ impl LocalUserLanguage { } impl SiteLanguage { - pub async fn read_local(pool: &DbPool) -> Result, Error> { + pub async fn read_local_raw(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; site::table .inner_join(local_site::table) @@ -109,26 +125,33 @@ impl SiteLanguage { .await } - pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result, Error> { + pub async fn read(pool: &mut DbPool<'_>, for_site_id: SiteId) -> Result, Error> { let conn = &mut get_conn(pool).await?; - let langs = site_language::table .filter(site_language::site_id.eq(for_site_id)) .order(site_language::language_id) .select(site_language::language_id) .load(conn) .await?; + convert_read_languages(conn, langs).await } pub async fn update( - pool: &DbPool, + pool: &mut DbPool<'_>, language_ids: Vec, site: &Site, ) -> Result<(), Error> { let conn = &mut get_conn(pool).await?; let for_site_id = site.id; let instance_id = site.instance_id; + let lang_ids = convert_update_languages(conn, language_ids).await?; + + // No need to update if languages are unchanged + let current = SiteLanguage::read(&mut conn.into(), site.id).await?; + if current == lang_ids { + return Ok(()); + } conn .build_transaction() @@ -141,7 +164,6 @@ impl SiteLanguage { .execute(conn) .await?; - let lang_ids = convert_update_languages(conn, language_ids).await?; for l in lang_ids { let form = SiteLanguageForm { site_id: for_site_id, @@ -165,7 +187,7 @@ impl SiteLanguage { impl CommunityLanguage { /// Returns true if the given language is one of configured languages for given community pub async fn is_allowed_community_language( - pool: &DbPool, + pool: &mut DbPool<'_>, for_language_id: Option, for_community_id: CommunityId, ) -> Result<(), LemmyError> { @@ -184,7 +206,7 @@ impl CommunityLanguage { if is_allowed { Ok(()) } else { - Err(LemmyError::from_message("language_not_allowed")) + Err(LemmyErrorType::LanguageNotAllowed)? } } else { Ok(()) @@ -222,12 +244,11 @@ impl CommunityLanguage { } pub async fn read( - pool: &DbPool, + pool: &mut DbPool<'_>, for_community_id: CommunityId, ) -> Result, Error> { use crate::schema::community_language::dsl::{community_id, community_language, language_id}; let conn = &mut get_conn(pool).await?; - let langs = community_language .filter(community_id.eq(for_community_id)) .order(language_id) @@ -238,35 +259,53 @@ impl CommunityLanguage { } pub async fn update( - pool: &DbPool, + pool: &mut DbPool<'_>, mut language_ids: Vec, for_community_id: CommunityId, ) -> Result<(), Error> { + if language_ids.is_empty() { + language_ids = SiteLanguage::read_local_raw(pool).await?; + } let conn = &mut get_conn(pool).await?; + let lang_ids = convert_update_languages(conn, language_ids).await?; - if language_ids.is_empty() { - language_ids = SiteLanguage::read_local(pool).await?; + // No need to update if languages are unchanged + let current = CommunityLanguage::read(&mut conn.into(), for_community_id).await?; + if current == lang_ids { + return Ok(()); } + let form = lang_ids + .into_iter() + .map(|language_id| CommunityLanguageForm { + community_id: for_community_id, + language_id, + }) + .collect::>(); + conn .build_transaction() .run(|conn| { Box::pin(async move { use crate::schema::community_language::dsl::{community_id, community_language}; + use diesel::result::DatabaseErrorKind::UniqueViolation; // Clear the current languages delete(community_language.filter(community_id.eq(for_community_id))) .execute(conn) .await?; - for l in language_ids { - let form = CommunityLanguageForm { - community_id: for_community_id, - language_id: l, - }; - insert_into(community_language) - .values(form) - .get_result::(conn) - .await?; + let insert_res = insert_into(community_language) + .values(form) + .get_result::(conn) + .await; + + if let Err(Error::DatabaseError(UniqueViolation, _info)) = insert_res { + // race condition: this function was probably called simultaneously from another caller. ignore error + // tracing::warn!("unique error: {_info:#?}"); + // _info.constraint_name() should be = "community_language_community_id_language_id_key" + return Ok(()); + } else { + insert_res?; } Ok(()) }) as _ @@ -276,7 +315,7 @@ impl CommunityLanguage { } pub async fn default_post_language( - pool: &DbPool, + pool: &mut DbPool<'_>, community_id: CommunityId, local_user_id: LocalUserId, ) -> Result, Error> { @@ -307,7 +346,7 @@ async fn convert_update_languages( ) -> Result, Error> { if language_ids.is_empty() { Ok( - Language::read_all_conn(conn) + Language::read_all(&mut conn.into()) .await? .into_iter() .map(|l| l.id) @@ -345,6 +384,9 @@ async fn convert_read_languages( #[cfg(test)] mod tests { + #![allow(clippy::unwrap_used)] + #![allow(clippy::indexing_slicing)] + use super::*; use crate::{ impls::actor_language::{ @@ -374,7 +416,7 @@ mod tests { }; use serial_test::serial; - async fn test_langs1(pool: &DbPool) -> Vec { + async fn test_langs1(pool: &mut DbPool<'_>) -> Vec { vec![ Language::read_id_from_code(pool, Some("en")) .await @@ -390,7 +432,7 @@ mod tests { .unwrap(), ] } - async fn test_langs2(pool: &DbPool) -> Vec { + async fn test_langs2(pool: &mut DbPool<'_>) -> Vec { vec![ Language::read_id_from_code(pool, Some("fi")) .await @@ -403,7 +445,7 @@ mod tests { ] } - async fn create_test_site(pool: &DbPool) -> (Site, Instance) { + async fn create_test_site(pool: &mut DbPool<'_>) -> (Site, Instance) { let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) .await .unwrap(); @@ -425,6 +467,7 @@ mod tests { #[serial] async fn test_convert_update_languages() { let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); // call with empty vec, returns all languages let conn = &mut get_conn(pool).await.unwrap(); @@ -432,7 +475,7 @@ mod tests { assert_eq!(184, converted1.len()); // call with nonempty vec, returns same vec - let test_langs = test_langs1(pool).await; + let test_langs = test_langs1(&mut conn.into()).await; let converted2 = convert_update_languages(conn, test_langs.clone()) .await .unwrap(); @@ -443,6 +486,7 @@ mod tests { async fn test_convert_read_languages() { use crate::schema::language::dsl::{id, language}; let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); // call with all languages, returns empty vec let conn = &mut get_conn(pool).await.unwrap(); @@ -451,7 +495,7 @@ mod tests { assert_eq!(0, converted1.len()); // call with nonempty vec, returns same vec - let test_langs = test_langs1(pool).await; + let test_langs = test_langs1(&mut conn.into()).await; let converted2 = convert_read_languages(conn, test_langs.clone()) .await .unwrap(); @@ -462,9 +506,10 @@ mod tests { #[serial] async fn test_site_languages() { let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); let (site, instance) = create_test_site(pool).await; - let site_languages1 = SiteLanguage::read_local(pool).await.unwrap(); + let site_languages1 = SiteLanguage::read_local_raw(pool).await.unwrap(); // site is created with all languages assert_eq!(184, site_languages1.len()); @@ -473,7 +518,7 @@ mod tests { .await .unwrap(); - let site_languages2 = SiteLanguage::read_local(pool).await.unwrap(); + let site_languages2 = SiteLanguage::read_local_raw(pool).await.unwrap(); // after update, site only has new languages assert_eq!(test_langs, site_languages2); @@ -486,9 +531,10 @@ mod tests { #[serial] async fn test_user_languages() { let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); let (site, instance) = create_test_site(pool).await; - let test_langs = test_langs1(pool).await; + let mut test_langs = test_langs1(pool).await; SiteLanguage::update(pool, test_langs.clone(), &site) .await .unwrap(); @@ -507,7 +553,10 @@ mod tests { let local_user = LocalUser::create(pool, &local_user_form).await.unwrap(); let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await.unwrap(); - // new user should be initialized with site languages + // new user should be initialized with site languages and undetermined + //test_langs.push(UNDETERMINED_ID); + //test_langs.sort(); + test_langs.insert(0, UNDETERMINED_ID); assert_eq!(test_langs, local_user_langs1); // update user languages @@ -516,7 +565,7 @@ mod tests { .await .unwrap(); let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap(); - assert_eq!(2, local_user_langs2.len()); + assert_eq!(3, local_user_langs2.len()); Person::delete(pool, person.id).await.unwrap(); LocalUser::delete(pool, local_user.id).await.unwrap(); @@ -529,6 +578,7 @@ mod tests { #[serial] async fn test_community_languages() { let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); let (site, instance) = create_test_site(pool).await; let test_langs = test_langs1(pool).await; SiteLanguage::update(pool, test_langs.clone(), &site) @@ -539,7 +589,7 @@ mod tests { assert_eq!(test_langs, read_site_langs); // Test the local ones are the same - let read_local_site_langs = SiteLanguage::read_local(pool).await.unwrap(); + let read_local_site_langs = SiteLanguage::read_local_raw(pool).await.unwrap(); assert_eq!(test_langs, read_local_site_langs); let community_form = CommunityInsertForm::builder() @@ -590,9 +640,9 @@ mod tests { #[serial] async fn test_default_post_language() { let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); let (site, instance) = create_test_site(pool).await; - let mut test_langs = test_langs1(pool).await; - test_langs.push(UNDETERMINED_ID); + let test_langs = test_langs1(pool).await; let test_langs2 = test_langs2(pool).await; let community_form = CommunityInsertForm::builder() @@ -621,7 +671,7 @@ mod tests { .await .unwrap(); - // no overlap in user/community languages, so no default language for post + // no overlap in user/community languages, so defaults to undetermined let def1 = default_post_language(pool, community.id, local_user.id) .await .unwrap();