3 newtypes::{CommunityId, InstanceId, LanguageId, LocalUserId, SiteId},
4 schema::{local_site, site, site_language},
10 LocalUserLanguageForm,
17 utils::{get_conn, DbPool},
28 use diesel_async::{AsyncPgConnection, RunQueryDsl};
29 use lemmy_utils::error::LemmyError;
30 use tokio::sync::OnceCell;
32 pub const UNDETERMINED_ID: LanguageId = LanguageId(0);
34 impl LocalUserLanguage {
37 for_local_user_id: LocalUserId,
38 ) -> Result<Vec<LanguageId>, Error> {
39 use crate::schema::local_user_language::dsl::{
44 let conn = &mut get_conn(pool).await?;
50 let langs = local_user_language
51 .filter(local_user_id.eq(for_local_user_id))
56 convert_read_languages(conn, langs).await
62 /// Update the user's languages.
64 /// If no language_id vector is given, it will show all languages
67 language_ids: Vec<LanguageId>,
68 for_local_user_id: LocalUserId,
69 ) -> Result<(), Error> {
70 let conn = &mut get_conn(pool).await?;
76 use crate::schema::local_user_language::dsl::{local_user_id, local_user_language};
77 // Clear the current user languages
78 delete(local_user_language.filter(local_user_id.eq(for_local_user_id)))
82 let lang_ids = convert_update_languages(conn, language_ids).await?;
84 let form = LocalUserLanguageForm {
85 local_user_id: for_local_user_id,
88 insert_into(local_user_language)
90 .get_result::<Self>(conn)
101 pub async fn read_local(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
102 let conn = &mut get_conn(pool).await?;
104 .inner_join(local_site::table)
105 .inner_join(site_language::table)
106 .order(site_language::id)
107 .select(site_language::language_id)
112 pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
113 let conn = &mut get_conn(pool).await?;
115 let langs = site_language::table
116 .filter(site_language::site_id.eq(for_site_id))
117 .order(site_language::language_id)
118 .select(site_language::language_id)
121 convert_read_languages(conn, langs).await
126 language_ids: Vec<LanguageId>,
128 ) -> Result<(), Error> {
129 let conn = &mut get_conn(pool).await?;
130 let for_site_id = site.id;
131 let instance_id = site.instance_id;
136 Box::pin(async move {
137 use crate::schema::site_language::dsl::{site_id, site_language};
139 // Clear the current languages
140 delete(site_language.filter(site_id.eq(for_site_id)))
144 let lang_ids = convert_update_languages(conn, language_ids).await?;
146 let form = SiteLanguageForm {
147 site_id: for_site_id,
150 insert_into(site_language)
152 .get_result::<Self>(conn)
156 CommunityLanguage::limit_languages(conn, instance_id).await?;
165 impl CommunityLanguage {
166 /// Returns true if the given language is one of configured languages for given community
167 pub async fn is_allowed_community_language(
169 for_language_id: Option<LanguageId>,
170 for_community_id: CommunityId,
171 ) -> Result<(), LemmyError> {
172 use crate::schema::community_language::dsl::{community_id, community_language, language_id};
173 let conn = &mut get_conn(pool).await?;
175 if let Some(for_language_id) = for_language_id {
176 let is_allowed = select(exists(
178 .filter(language_id.eq(for_language_id))
179 .filter(community_id.eq(for_community_id)),
187 Err(LemmyError::from_message("language_not_allowed"))
194 /// When site languages are updated, delete all languages of local communities which are not
195 /// also part of site languages. This is because post/comment language is only checked against
196 /// community language, and it shouldnt be possible to post content in languages which are not
197 /// allowed by local site.
198 async fn limit_languages(
199 conn: &mut AsyncPgConnection,
200 for_instance_id: InstanceId,
201 ) -> Result<(), Error> {
204 community_language::dsl as cl,
205 site_language::dsl as sl,
207 let community_languages: Vec<LanguageId> = cl::community_language
208 .left_outer_join(sl::site_language.on(cl::language_id.eq(sl::language_id)))
209 .inner_join(c::community)
210 .filter(c::instance_id.eq(for_instance_id))
211 .filter(sl::language_id.is_null())
212 .select(cl::language_id)
216 for c in community_languages {
217 delete(cl::community_language.filter(cl::language_id.eq(c)))
226 for_community_id: CommunityId,
227 ) -> Result<Vec<LanguageId>, Error> {
228 use crate::schema::community_language::dsl::{community_id, community_language, language_id};
229 let conn = &mut get_conn(pool).await?;
231 let langs = community_language
232 .filter(community_id.eq(for_community_id))
237 convert_read_languages(conn, langs).await
242 mut language_ids: Vec<LanguageId>,
243 for_community_id: CommunityId,
244 ) -> Result<(), Error> {
245 let conn = &mut get_conn(pool).await?;
247 if language_ids.is_empty() {
248 language_ids = SiteLanguage::read_local(pool).await?;
254 Box::pin(async move {
255 use crate::schema::community_language::dsl::{community_id, community_language};
256 // Clear the current languages
257 delete(community_language.filter(community_id.eq(for_community_id)))
261 for l in language_ids {
262 let form = CommunityLanguageForm {
263 community_id: for_community_id,
266 insert_into(community_language)
268 .get_result::<Self>(conn)
278 pub async fn default_post_language(
280 community_id: CommunityId,
281 local_user_id: LocalUserId,
282 ) -> Result<Option<LanguageId>, Error> {
283 use crate::schema::{community_language::dsl as cl, local_user_language::dsl as ul};
284 let conn = &mut get_conn(pool).await?;
285 let mut intersection = ul::local_user_language
286 .inner_join(cl::community_language.on(ul::language_id.eq(cl::language_id)))
287 .filter(ul::local_user_id.eq(local_user_id))
288 .filter(cl::community_id.eq(community_id))
289 .select(cl::language_id)
290 .get_results::<LanguageId>(conn)
293 if intersection.len() == 1 {
294 Ok(intersection.pop())
295 } else if intersection.len() == 2 && intersection.contains(&UNDETERMINED_ID) {
296 intersection.retain(|i| i != &UNDETERMINED_ID);
297 Ok(intersection.pop())
303 /// If no language is given, set all languages
304 async fn convert_update_languages(
305 conn: &mut AsyncPgConnection,
306 language_ids: Vec<LanguageId>,
307 ) -> Result<Vec<LanguageId>, Error> {
308 if language_ids.is_empty() {
310 Language::read_all_conn(conn)
321 /// If all languages are returned, return empty vec instead
322 async fn convert_read_languages(
323 conn: &mut AsyncPgConnection,
324 language_ids: Vec<LanguageId>,
325 ) -> Result<Vec<LanguageId>, Error> {
326 static ALL_LANGUAGES_COUNT: OnceCell<usize> = OnceCell::const_new();
327 let count = ALL_LANGUAGES_COUNT
328 .get_or_init(|| async {
329 use crate::schema::language::dsl::{id, language};
330 let count: i64 = language
334 .expect("read number of languages");
339 if &language_ids.len() == count {
350 impls::actor_language::{
351 convert_read_languages,
352 convert_update_languages,
353 default_post_language,
365 community::{Community, CommunityInsertForm},
367 local_site::{LocalSite, LocalSiteInsertForm},
368 local_user::{LocalUser, LocalUserInsertForm},
369 person::{Person, PersonInsertForm},
370 site::{Site, SiteInsertForm},
373 utils::build_db_pool_for_tests,
375 use serial_test::serial;
377 async fn test_langs1(pool: &DbPool) -> Vec<LanguageId> {
379 Language::read_id_from_code(pool, Some("en"))
383 Language::read_id_from_code(pool, Some("fr"))
387 Language::read_id_from_code(pool, Some("ru"))
393 async fn test_langs2(pool: &DbPool) -> Vec<LanguageId> {
395 Language::read_id_from_code(pool, Some("fi"))
399 Language::read_id_from_code(pool, Some("se"))
406 async fn create_test_site(pool: &DbPool) -> (Site, Instance) {
407 let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
411 let site_form = SiteInsertForm::builder()
412 .name("test site".to_string())
413 .instance_id(inserted_instance.id)
415 let site = Site::create(pool, &site_form).await.unwrap();
417 // Create a local site, since this is necessary for local languages
418 let local_site_form = LocalSiteInsertForm::builder().site_id(site.id).build();
419 LocalSite::create(pool, &local_site_form).await.unwrap();
421 (site, inserted_instance)
426 async fn test_convert_update_languages() {
427 let pool = &build_db_pool_for_tests().await;
429 // call with empty vec, returns all languages
430 let conn = &mut get_conn(pool).await.unwrap();
431 let converted1 = convert_update_languages(conn, vec![]).await.unwrap();
432 assert_eq!(184, converted1.len());
434 // call with nonempty vec, returns same vec
435 let test_langs = test_langs1(pool).await;
436 let converted2 = convert_update_languages(conn, test_langs.clone())
439 assert_eq!(test_langs, converted2);
443 async fn test_convert_read_languages() {
444 use crate::schema::language::dsl::{id, language};
445 let pool = &build_db_pool_for_tests().await;
447 // call with all languages, returns empty vec
448 let conn = &mut get_conn(pool).await.unwrap();
449 let all_langs = language.select(id).get_results(conn).await.unwrap();
450 let converted1: Vec<LanguageId> = convert_read_languages(conn, all_langs).await.unwrap();
451 assert_eq!(0, converted1.len());
453 // call with nonempty vec, returns same vec
454 let test_langs = test_langs1(pool).await;
455 let converted2 = convert_read_languages(conn, test_langs.clone())
458 assert_eq!(test_langs, converted2);
463 async fn test_site_languages() {
464 let pool = &build_db_pool_for_tests().await;
466 let (site, instance) = create_test_site(pool).await;
467 let site_languages1 = SiteLanguage::read_local(pool).await.unwrap();
468 // site is created with all languages
469 assert_eq!(184, site_languages1.len());
471 let test_langs = test_langs1(pool).await;
472 SiteLanguage::update(pool, test_langs.clone(), &site)
476 let site_languages2 = SiteLanguage::read_local(pool).await.unwrap();
477 // after update, site only has new languages
478 assert_eq!(test_langs, site_languages2);
480 Site::delete(pool, site.id).await.unwrap();
481 Instance::delete(pool, instance.id).await.unwrap();
482 LocalSite::delete(pool).await.unwrap();
487 async fn test_user_languages() {
488 let pool = &build_db_pool_for_tests().await;
490 let (site, instance) = create_test_site(pool).await;
491 let test_langs = test_langs1(pool).await;
492 SiteLanguage::update(pool, test_langs.clone(), &site)
496 let person_form = PersonInsertForm::builder()
497 .name("my test person".to_string())
498 .public_key("pubkey".to_string())
499 .instance_id(instance.id)
501 let person = Person::create(pool, &person_form).await.unwrap();
502 let local_user_form = LocalUserInsertForm::builder()
503 .person_id(person.id)
504 .password_encrypted("my_pw".to_string())
507 let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
508 let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
510 // new user should be initialized with site languages
511 assert_eq!(test_langs, local_user_langs1);
513 // update user languages
514 let test_langs2 = test_langs2(pool).await;
515 LocalUserLanguage::update(pool, test_langs2, local_user.id)
518 let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
519 assert_eq!(2, local_user_langs2.len());
521 Person::delete(pool, person.id).await.unwrap();
522 LocalUser::delete(pool, local_user.id).await.unwrap();
523 Site::delete(pool, site.id).await.unwrap();
524 LocalSite::delete(pool).await.unwrap();
525 Instance::delete(pool, instance.id).await.unwrap();
530 async fn test_community_languages() {
531 let pool = &build_db_pool_for_tests().await;
532 let (site, instance) = create_test_site(pool).await;
533 let test_langs = test_langs1(pool).await;
534 SiteLanguage::update(pool, test_langs.clone(), &site)
538 let read_site_langs = SiteLanguage::read(pool, site.id).await.unwrap();
539 assert_eq!(test_langs, read_site_langs);
541 // Test the local ones are the same
542 let read_local_site_langs = SiteLanguage::read_local(pool).await.unwrap();
543 assert_eq!(test_langs, read_local_site_langs);
545 let community_form = CommunityInsertForm::builder()
546 .name("test community".to_string())
547 .title("test community".to_string())
548 .public_key("pubkey".to_string())
549 .instance_id(instance.id)
551 let community = Community::create(pool, &community_form).await.unwrap();
552 let community_langs1 = CommunityLanguage::read(pool, community.id).await.unwrap();
554 // community is initialized with site languages
555 assert_eq!(test_langs, community_langs1);
558 CommunityLanguage::is_allowed_community_language(pool, Some(test_langs[0]), community.id)
560 assert!(allowed_lang1.is_ok());
562 let test_langs2 = test_langs2(pool).await;
564 CommunityLanguage::is_allowed_community_language(pool, Some(test_langs2[0]), community.id)
566 assert!(allowed_lang2.is_err());
568 // limit site languages to en, fi. after this, community languages should be updated to
569 // intersection of old languages (en, fr, ru) and (en, fi), which is only fi.
570 SiteLanguage::update(pool, vec![test_langs[0], test_langs2[0]], &site)
573 let community_langs2 = CommunityLanguage::read(pool, community.id).await.unwrap();
574 assert_eq!(vec![test_langs[0]], community_langs2);
576 // update community languages to different ones
577 CommunityLanguage::update(pool, test_langs2.clone(), community.id)
580 let community_langs3 = CommunityLanguage::read(pool, community.id).await.unwrap();
581 assert_eq!(test_langs2, community_langs3);
583 Community::delete(pool, community.id).await.unwrap();
584 Site::delete(pool, site.id).await.unwrap();
585 LocalSite::delete(pool).await.unwrap();
586 Instance::delete(pool, instance.id).await.unwrap();
591 async fn test_default_post_language() {
592 let pool = &build_db_pool_for_tests().await;
593 let (site, instance) = create_test_site(pool).await;
594 let mut test_langs = test_langs1(pool).await;
595 test_langs.push(UNDETERMINED_ID);
596 let test_langs2 = test_langs2(pool).await;
598 let community_form = CommunityInsertForm::builder()
599 .name("test community".to_string())
600 .title("test community".to_string())
601 .public_key("pubkey".to_string())
602 .instance_id(instance.id)
604 let community = Community::create(pool, &community_form).await.unwrap();
605 CommunityLanguage::update(pool, test_langs, community.id)
609 let person_form = PersonInsertForm::builder()
610 .name("my test person".to_string())
611 .public_key("pubkey".to_string())
612 .instance_id(instance.id)
614 let person = Person::create(pool, &person_form).await.unwrap();
615 let local_user_form = LocalUserInsertForm::builder()
616 .person_id(person.id)
617 .password_encrypted("my_pw".to_string())
619 let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
620 LocalUserLanguage::update(pool, test_langs2, local_user.id)
624 // no overlap in user/community languages, so no default language for post
625 let def1 = default_post_language(pool, community.id, local_user.id)
628 assert_eq!(None, def1);
630 let ru = Language::read_id_from_code(pool, Some("ru"))
634 let test_langs3 = vec![
636 Language::read_id_from_code(pool, Some("fi"))
640 Language::read_id_from_code(pool, Some("se"))
646 LocalUserLanguage::update(pool, test_langs3, local_user.id)
650 // this time, both have ru as common lang
651 let def2 = default_post_language(pool, community.id, local_user.id)
654 assert_eq!(Some(ru), def2);
656 Person::delete(pool, person.id).await.unwrap();
657 Community::delete(pool, community.id).await.unwrap();
658 LocalUser::delete(pool, local_user.id).await.unwrap();
659 Site::delete(pool, site.id).await.unwrap();
660 LocalSite::delete(pool).await.unwrap();
661 Instance::delete(pool, instance.id).await.unwrap();