]> Untitled Git - lemmy.git/blobdiff - crates/db_schema/src/impls/actor_language.rs
Cache & Optimize Woodpecker CI (#3450)
[lemmy.git] / crates / db_schema / src / impls / actor_language.rs
index c04982417512bf4260dee9285948d7703cc27aed..313762a72f97c070229989d2f3e5711a23d3060d 100644 (file)
@@ -26,12 +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<Vec<LanguageId>, Error> {
     use crate::schema::local_user_language::dsl::{
@@ -61,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<LanguageId>,
     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()
@@ -77,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,
@@ -96,7 +114,7 @@ impl LocalUserLanguage {
 }
 
 impl SiteLanguage {
-  pub async fn read_local(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
+  pub async fn read_local_raw(pool: &mut DbPool<'_>) -> Result<Vec<LanguageId>, Error> {
     let conn = &mut get_conn(pool).await?;
     site::table
       .inner_join(local_site::table)
@@ -107,26 +125,33 @@ impl SiteLanguage {
       .await
   }
 
-  pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
+  pub async fn read(pool: &mut DbPool<'_>, for_site_id: SiteId) -> Result<Vec<LanguageId>, 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<LanguageId>,
     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()
@@ -139,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,
@@ -163,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<LanguageId>,
     for_community_id: CommunityId,
   ) -> Result<(), LemmyError> {
@@ -182,7 +206,7 @@ impl CommunityLanguage {
       if is_allowed {
         Ok(())
       } else {
-        Err(LemmyError::from_message("language_not_allowed"))
+        Err(LemmyErrorType::LanguageNotAllowed)?
       }
     } else {
       Ok(())
@@ -220,12 +244,11 @@ impl CommunityLanguage {
   }
 
   pub async fn read(
-    pool: &DbPool,
+    pool: &mut DbPool<'_>,
     for_community_id: CommunityId,
   ) -> Result<Vec<LanguageId>, 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)
@@ -236,35 +259,53 @@ impl CommunityLanguage {
   }
 
   pub async fn update(
-    pool: &DbPool,
+    pool: &mut DbPool<'_>,
     mut language_ids: Vec<LanguageId>,
     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::<Vec<_>>();
+
     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::<Self>(conn)
-              .await?;
+          let insert_res = insert_into(community_language)
+            .values(form)
+            .get_result::<Self>(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 _
@@ -274,13 +315,13 @@ impl CommunityLanguage {
 }
 
 pub async fn default_post_language(
-  pool: &DbPool,
+  pool: &mut DbPool<'_>,
   community_id: CommunityId,
   local_user_id: LocalUserId,
 ) -> Result<Option<LanguageId>, Error> {
   use crate::schema::{community_language::dsl as cl, local_user_language::dsl as ul};
   let conn = &mut get_conn(pool).await?;
-  let intersection = ul::local_user_language
+  let mut intersection = ul::local_user_language
     .inner_join(cl::community_language.on(ul::language_id.eq(cl::language_id)))
     .filter(ul::local_user_id.eq(local_user_id))
     .filter(cl::community_id.eq(community_id))
@@ -289,7 +330,10 @@ pub async fn default_post_language(
     .await?;
 
   if intersection.len() == 1 {
-    Ok(Some(intersection[0]))
+    Ok(intersection.pop())
+  } else if intersection.len() == 2 && intersection.contains(&UNDETERMINED_ID) {
+    intersection.retain(|i| i != &UNDETERMINED_ID);
+    Ok(intersection.pop())
   } else {
     Ok(None)
   }
@@ -302,7 +346,7 @@ async fn convert_update_languages(
 ) -> Result<Vec<LanguageId>, 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)
@@ -340,6 +384,10 @@ async fn convert_read_languages(
 
 #[cfg(test)]
 mod tests {
+  #![allow(clippy::unwrap_used)]
+  #![allow(clippy::indexing_slicing)]
+
+  use super::*;
   use crate::{
     impls::actor_language::{
       convert_read_languages,
@@ -368,7 +416,7 @@ mod tests {
   };
   use serial_test::serial;
 
-  async fn test_langs1(pool: &DbPool) -> Vec<LanguageId> {
+  async fn test_langs1(pool: &mut DbPool<'_>) -> Vec<LanguageId> {
     vec![
       Language::read_id_from_code(pool, Some("en"))
         .await
@@ -384,7 +432,7 @@ mod tests {
         .unwrap(),
     ]
   }
-  async fn test_langs2(pool: &DbPool) -> Vec<LanguageId> {
+  async fn test_langs2(pool: &mut DbPool<'_>) -> Vec<LanguageId> {
     vec![
       Language::read_id_from_code(pool, Some("fi"))
         .await
@@ -397,8 +445,10 @@ mod tests {
     ]
   }
 
-  async fn create_test_site(pool: &DbPool) -> (Site, Instance) {
-    let inserted_instance = Instance::create(pool, "my_domain.tld").await.unwrap();
+  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();
 
     let site_form = SiteInsertForm::builder()
       .name("test site".to_string())
@@ -417,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();
@@ -424,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();
@@ -435,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();
@@ -443,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();
@@ -454,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());
 
@@ -465,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);
 
@@ -478,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();
@@ -499,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
@@ -508,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();
@@ -521,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)
@@ -531,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()
@@ -582,6 +640,7 @@ 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 test_langs = test_langs1(pool).await;
     let test_langs2 = test_langs2(pool).await;
@@ -612,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();
@@ -632,6 +691,7 @@ mod tests {
         .await
         .unwrap()
         .unwrap(),
+      UNDETERMINED_ID,
     ];
     LocalUserLanguage::update(pool, test_langs3, local_user.id)
       .await