3 newtypes::{CommunityId, InstanceId, LanguageId, LocalUserId, SiteId},
4 schema::{local_site, site, site_language},
10 LocalUserLanguageForm,
17 utils::{get_conn, DbPool},
29 pooled_connection::deadpool::Object as PooledConnection,
33 use lemmy_utils::error::{LemmyError, LemmyErrorType};
34 use tokio::sync::OnceCell;
36 pub const UNDETERMINED_ID: LanguageId = LanguageId(0);
38 impl LocalUserLanguage {
41 for_local_user_id: LocalUserId,
42 ) -> Result<Vec<LanguageId>, Error> {
43 use crate::schema::local_user_language::dsl::{
48 let conn = &mut get_conn(pool).await?;
54 let langs = local_user_language
55 .filter(local_user_id.eq(for_local_user_id))
60 convert_read_languages(conn, langs).await
66 /// Update the user's languages.
68 /// If no language_id vector is given, it will show all languages
71 language_ids: Vec<LanguageId>,
72 for_local_user_id: LocalUserId,
73 ) -> Result<(), Error> {
74 let conn = &mut get_conn(pool).await?;
75 let mut lang_ids = convert_update_languages(conn, language_ids).await?;
77 // No need to update if languages are unchanged
78 let current = LocalUserLanguage::read(pool, for_local_user_id).await?;
79 if current == lang_ids {
83 // TODO: Force enable undetermined language for all users. This is necessary because many posts
84 // don't have a language tag (e.g. those from other federated platforms), so Lemmy users
85 // won't see them if undetermined language is disabled.
86 // This hack can be removed once a majority of posts have language tags, or when it is
87 // clearer for new users that they need to enable undetermined language.
88 // See https://github.com/LemmyNet/lemmy-ui/issues/999
89 if !lang_ids.contains(&UNDETERMINED_ID) {
90 lang_ids.push(UNDETERMINED_ID);
97 use crate::schema::local_user_language::dsl::{local_user_id, local_user_language};
98 // Clear the current user languages
99 delete(local_user_language.filter(local_user_id.eq(for_local_user_id)))
104 let form = LocalUserLanguageForm {
105 local_user_id: for_local_user_id,
108 insert_into(local_user_language)
110 .get_result::<Self>(conn)
121 pub async fn read_local_raw(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
122 let conn = &mut get_conn(pool).await?;
124 .inner_join(local_site::table)
125 .inner_join(site_language::table)
126 .order(site_language::id)
127 .select(site_language::language_id)
133 conn: &mut PooledConnection<AsyncPgConnection>,
135 ) -> Result<Vec<LanguageId>, Error> {
137 .filter(site_language::site_id.eq(for_site_id))
138 .order(site_language::language_id)
139 .select(site_language::language_id)
144 pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
145 let conn = &mut get_conn(pool).await?;
146 let langs = Self::read_raw(conn, for_site_id).await?;
148 convert_read_languages(conn, langs).await
153 language_ids: Vec<LanguageId>,
155 ) -> Result<(), Error> {
156 let conn = &mut get_conn(pool).await?;
157 let for_site_id = site.id;
158 let instance_id = site.instance_id;
159 let lang_ids = convert_update_languages(conn, language_ids).await?;
161 // No need to update if languages are unchanged
162 let current = SiteLanguage::read(pool, site.id).await?;
163 if current == lang_ids {
170 Box::pin(async move {
171 use crate::schema::site_language::dsl::{site_id, site_language};
173 // Clear the current languages
174 delete(site_language.filter(site_id.eq(for_site_id)))
179 let form = SiteLanguageForm {
180 site_id: for_site_id,
183 insert_into(site_language)
185 .get_result::<Self>(conn)
189 CommunityLanguage::limit_languages(conn, instance_id).await?;
198 impl CommunityLanguage {
199 /// Returns true if the given language is one of configured languages for given community
200 pub async fn is_allowed_community_language(
202 for_language_id: Option<LanguageId>,
203 for_community_id: CommunityId,
204 ) -> Result<(), LemmyError> {
205 use crate::schema::community_language::dsl::{community_id, community_language, language_id};
206 let conn = &mut get_conn(pool).await?;
208 if let Some(for_language_id) = for_language_id {
209 let is_allowed = select(exists(
211 .filter(language_id.eq(for_language_id))
212 .filter(community_id.eq(for_community_id)),
220 Err(LemmyErrorType::LanguageNotAllowed)?
227 /// When site languages are updated, delete all languages of local communities which are not
228 /// also part of site languages. This is because post/comment language is only checked against
229 /// community language, and it shouldnt be possible to post content in languages which are not
230 /// allowed by local site.
231 async fn limit_languages(
232 conn: &mut AsyncPgConnection,
233 for_instance_id: InstanceId,
234 ) -> Result<(), Error> {
237 community_language::dsl as cl,
238 site_language::dsl as sl,
240 let community_languages: Vec<LanguageId> = cl::community_language
241 .left_outer_join(sl::site_language.on(cl::language_id.eq(sl::language_id)))
242 .inner_join(c::community)
243 .filter(c::instance_id.eq(for_instance_id))
244 .filter(sl::language_id.is_null())
245 .select(cl::language_id)
249 for c in community_languages {
250 delete(cl::community_language.filter(cl::language_id.eq(c)))
258 conn: &mut PooledConnection<AsyncPgConnection>,
259 for_community_id: CommunityId,
260 ) -> Result<Vec<LanguageId>, Error> {
261 use crate::schema::community_language::dsl::{community_id, community_language, language_id};
263 .filter(community_id.eq(for_community_id))
272 for_community_id: CommunityId,
273 ) -> Result<Vec<LanguageId>, Error> {
274 let conn = &mut get_conn(pool).await?;
275 let langs = Self::read_raw(conn, for_community_id).await?;
276 convert_read_languages(conn, langs).await
281 mut language_ids: Vec<LanguageId>,
282 for_community_id: CommunityId,
283 ) -> Result<(), Error> {
284 let conn = &mut get_conn(pool).await?;
285 if language_ids.is_empty() {
286 language_ids = SiteLanguage::read_local_raw(pool).await?;
288 let lang_ids = convert_update_languages(conn, language_ids).await?;
290 // No need to update if languages are unchanged
291 let current = CommunityLanguage::read_raw(conn, for_community_id).await?;
292 if current == lang_ids {
299 Box::pin(async move {
300 use crate::schema::community_language::dsl::{community_id, community_language};
301 // Clear the current languages
302 delete(community_language.filter(community_id.eq(for_community_id)))
307 let form = CommunityLanguageForm {
308 community_id: for_community_id,
311 insert_into(community_language)
313 .get_result::<Self>(conn)
323 pub async fn default_post_language(
325 community_id: CommunityId,
326 local_user_id: LocalUserId,
327 ) -> Result<Option<LanguageId>, Error> {
328 use crate::schema::{community_language::dsl as cl, local_user_language::dsl as ul};
329 let conn = &mut get_conn(pool).await?;
330 let mut intersection = ul::local_user_language
331 .inner_join(cl::community_language.on(ul::language_id.eq(cl::language_id)))
332 .filter(ul::local_user_id.eq(local_user_id))
333 .filter(cl::community_id.eq(community_id))
334 .select(cl::language_id)
335 .get_results::<LanguageId>(conn)
338 if intersection.len() == 1 {
339 Ok(intersection.pop())
340 } else if intersection.len() == 2 && intersection.contains(&UNDETERMINED_ID) {
341 intersection.retain(|i| i != &UNDETERMINED_ID);
342 Ok(intersection.pop())
348 /// If no language is given, set all languages
349 async fn convert_update_languages(
350 conn: &mut AsyncPgConnection,
351 language_ids: Vec<LanguageId>,
352 ) -> Result<Vec<LanguageId>, Error> {
353 if language_ids.is_empty() {
355 Language::read_all_conn(conn)
366 /// If all languages are returned, return empty vec instead
367 async fn convert_read_languages(
368 conn: &mut AsyncPgConnection,
369 language_ids: Vec<LanguageId>,
370 ) -> Result<Vec<LanguageId>, Error> {
371 static ALL_LANGUAGES_COUNT: OnceCell<usize> = OnceCell::const_new();
372 let count = ALL_LANGUAGES_COUNT
373 .get_or_init(|| async {
374 use crate::schema::language::dsl::{id, language};
375 let count: i64 = language
379 .expect("read number of languages");
384 if &language_ids.len() == count {
395 impls::actor_language::{
396 convert_read_languages,
397 convert_update_languages,
398 default_post_language,
410 community::{Community, CommunityInsertForm},
412 local_site::{LocalSite, LocalSiteInsertForm},
413 local_user::{LocalUser, LocalUserInsertForm},
414 person::{Person, PersonInsertForm},
415 site::{Site, SiteInsertForm},
418 utils::build_db_pool_for_tests,
420 use serial_test::serial;
422 async fn test_langs1(pool: &DbPool) -> Vec<LanguageId> {
424 Language::read_id_from_code(pool, Some("en"))
428 Language::read_id_from_code(pool, Some("fr"))
432 Language::read_id_from_code(pool, Some("ru"))
438 async fn test_langs2(pool: &DbPool) -> Vec<LanguageId> {
440 Language::read_id_from_code(pool, Some("fi"))
444 Language::read_id_from_code(pool, Some("se"))
451 async fn create_test_site(pool: &DbPool) -> (Site, Instance) {
452 let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
456 let site_form = SiteInsertForm::builder()
457 .name("test site".to_string())
458 .instance_id(inserted_instance.id)
460 let site = Site::create(pool, &site_form).await.unwrap();
462 // Create a local site, since this is necessary for local languages
463 let local_site_form = LocalSiteInsertForm::builder().site_id(site.id).build();
464 LocalSite::create(pool, &local_site_form).await.unwrap();
466 (site, inserted_instance)
471 async fn test_convert_update_languages() {
472 let pool = &build_db_pool_for_tests().await;
474 // call with empty vec, returns all languages
475 let conn = &mut get_conn(pool).await.unwrap();
476 let converted1 = convert_update_languages(conn, vec![]).await.unwrap();
477 assert_eq!(184, converted1.len());
479 // call with nonempty vec, returns same vec
480 let test_langs = test_langs1(pool).await;
481 let converted2 = convert_update_languages(conn, test_langs.clone())
484 assert_eq!(test_langs, converted2);
488 async fn test_convert_read_languages() {
489 use crate::schema::language::dsl::{id, language};
490 let pool = &build_db_pool_for_tests().await;
492 // call with all languages, returns empty vec
493 let conn = &mut get_conn(pool).await.unwrap();
494 let all_langs = language.select(id).get_results(conn).await.unwrap();
495 let converted1: Vec<LanguageId> = convert_read_languages(conn, all_langs).await.unwrap();
496 assert_eq!(0, converted1.len());
498 // call with nonempty vec, returns same vec
499 let test_langs = test_langs1(pool).await;
500 let converted2 = convert_read_languages(conn, test_langs.clone())
503 assert_eq!(test_langs, converted2);
508 async fn test_site_languages() {
509 let pool = &build_db_pool_for_tests().await;
511 let (site, instance) = create_test_site(pool).await;
512 let site_languages1 = SiteLanguage::read_local_raw(pool).await.unwrap();
513 // site is created with all languages
514 assert_eq!(184, site_languages1.len());
516 let test_langs = test_langs1(pool).await;
517 SiteLanguage::update(pool, test_langs.clone(), &site)
521 let site_languages2 = SiteLanguage::read_local_raw(pool).await.unwrap();
522 // after update, site only has new languages
523 assert_eq!(test_langs, site_languages2);
525 Site::delete(pool, site.id).await.unwrap();
526 Instance::delete(pool, instance.id).await.unwrap();
527 LocalSite::delete(pool).await.unwrap();
532 async fn test_user_languages() {
533 let pool = &build_db_pool_for_tests().await;
535 let (site, instance) = create_test_site(pool).await;
536 let mut test_langs = test_langs1(pool).await;
537 SiteLanguage::update(pool, test_langs.clone(), &site)
541 let person_form = PersonInsertForm::builder()
542 .name("my test person".to_string())
543 .public_key("pubkey".to_string())
544 .instance_id(instance.id)
546 let person = Person::create(pool, &person_form).await.unwrap();
547 let local_user_form = LocalUserInsertForm::builder()
548 .person_id(person.id)
549 .password_encrypted("my_pw".to_string())
552 let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
553 let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
555 // new user should be initialized with site languages and undetermined
556 //test_langs.push(UNDETERMINED_ID);
558 test_langs.insert(0, UNDETERMINED_ID);
559 assert_eq!(test_langs, local_user_langs1);
561 // update user languages
562 let test_langs2 = test_langs2(pool).await;
563 LocalUserLanguage::update(pool, test_langs2, local_user.id)
566 let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
567 assert_eq!(3, local_user_langs2.len());
569 Person::delete(pool, person.id).await.unwrap();
570 LocalUser::delete(pool, local_user.id).await.unwrap();
571 Site::delete(pool, site.id).await.unwrap();
572 LocalSite::delete(pool).await.unwrap();
573 Instance::delete(pool, instance.id).await.unwrap();
578 async fn test_community_languages() {
579 let pool = &build_db_pool_for_tests().await;
580 let (site, instance) = create_test_site(pool).await;
581 let test_langs = test_langs1(pool).await;
582 SiteLanguage::update(pool, test_langs.clone(), &site)
586 let read_site_langs = SiteLanguage::read(pool, site.id).await.unwrap();
587 assert_eq!(test_langs, read_site_langs);
589 // Test the local ones are the same
590 let read_local_site_langs = SiteLanguage::read_local_raw(pool).await.unwrap();
591 assert_eq!(test_langs, read_local_site_langs);
593 let community_form = CommunityInsertForm::builder()
594 .name("test community".to_string())
595 .title("test community".to_string())
596 .public_key("pubkey".to_string())
597 .instance_id(instance.id)
599 let community = Community::create(pool, &community_form).await.unwrap();
600 let community_langs1 = CommunityLanguage::read(pool, community.id).await.unwrap();
602 // community is initialized with site languages
603 assert_eq!(test_langs, community_langs1);
606 CommunityLanguage::is_allowed_community_language(pool, Some(test_langs[0]), community.id)
608 assert!(allowed_lang1.is_ok());
610 let test_langs2 = test_langs2(pool).await;
612 CommunityLanguage::is_allowed_community_language(pool, Some(test_langs2[0]), community.id)
614 assert!(allowed_lang2.is_err());
616 // limit site languages to en, fi. after this, community languages should be updated to
617 // intersection of old languages (en, fr, ru) and (en, fi), which is only fi.
618 SiteLanguage::update(pool, vec![test_langs[0], test_langs2[0]], &site)
621 let community_langs2 = CommunityLanguage::read(pool, community.id).await.unwrap();
622 assert_eq!(vec![test_langs[0]], community_langs2);
624 // update community languages to different ones
625 CommunityLanguage::update(pool, test_langs2.clone(), community.id)
628 let community_langs3 = CommunityLanguage::read(pool, community.id).await.unwrap();
629 assert_eq!(test_langs2, community_langs3);
631 Community::delete(pool, community.id).await.unwrap();
632 Site::delete(pool, site.id).await.unwrap();
633 LocalSite::delete(pool).await.unwrap();
634 Instance::delete(pool, instance.id).await.unwrap();
639 async fn test_default_post_language() {
640 let pool = &build_db_pool_for_tests().await;
641 let (site, instance) = create_test_site(pool).await;
642 let test_langs = test_langs1(pool).await;
643 let test_langs2 = test_langs2(pool).await;
645 let community_form = CommunityInsertForm::builder()
646 .name("test community".to_string())
647 .title("test community".to_string())
648 .public_key("pubkey".to_string())
649 .instance_id(instance.id)
651 let community = Community::create(pool, &community_form).await.unwrap();
652 CommunityLanguage::update(pool, test_langs, community.id)
656 let person_form = PersonInsertForm::builder()
657 .name("my test person".to_string())
658 .public_key("pubkey".to_string())
659 .instance_id(instance.id)
661 let person = Person::create(pool, &person_form).await.unwrap();
662 let local_user_form = LocalUserInsertForm::builder()
663 .person_id(person.id)
664 .password_encrypted("my_pw".to_string())
666 let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
667 LocalUserLanguage::update(pool, test_langs2, local_user.id)
671 // no overlap in user/community languages, so defaults to undetermined
672 let def1 = default_post_language(pool, community.id, local_user.id)
675 assert_eq!(None, def1);
677 let ru = Language::read_id_from_code(pool, Some("ru"))
681 let test_langs3 = vec![
683 Language::read_id_from_code(pool, Some("fi"))
687 Language::read_id_from_code(pool, Some("se"))
693 LocalUserLanguage::update(pool, test_langs3, local_user.id)
697 // this time, both have ru as common lang
698 let def2 = default_post_language(pool, community.id, local_user.id)
701 assert_eq!(Some(ru), def2);
703 Person::delete(pool, person.id).await.unwrap();
704 Community::delete(pool, community.id).await.unwrap();
705 LocalUser::delete(pool, local_user.id).await.unwrap();
706 Site::delete(pool, site.id).await.unwrap();
707 LocalSite::delete(pool).await.unwrap();
708 Instance::delete(pool, instance.id).await.unwrap();