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;
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 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 {
87 use crate::schema::local_user_language::dsl::{local_user_id, local_user_language};
88 // Clear the current user languages
89 delete(local_user_language.filter(local_user_id.eq(for_local_user_id)))
94 let form = LocalUserLanguageForm {
95 local_user_id: for_local_user_id,
98 insert_into(local_user_language)
100 .get_result::<Self>(conn)
111 pub async fn read_local_raw(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
112 let conn = &mut get_conn(pool).await?;
114 .inner_join(local_site::table)
115 .inner_join(site_language::table)
116 .order(site_language::id)
117 .select(site_language::language_id)
123 conn: &mut PooledConnection<AsyncPgConnection>,
125 ) -> Result<Vec<LanguageId>, Error> {
127 .filter(site_language::site_id.eq(for_site_id))
128 .order(site_language::language_id)
129 .select(site_language::language_id)
134 pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
135 let conn = &mut get_conn(pool).await?;
136 let langs = Self::read_raw(conn, for_site_id).await?;
138 convert_read_languages(conn, langs).await
143 language_ids: Vec<LanguageId>,
145 ) -> Result<(), Error> {
146 let conn = &mut get_conn(pool).await?;
147 let for_site_id = site.id;
148 let instance_id = site.instance_id;
149 let lang_ids = convert_update_languages(conn, language_ids).await?;
151 // No need to update if languages are unchanged
152 let current = SiteLanguage::read(pool, site.id).await?;
153 if current == lang_ids {
160 Box::pin(async move {
161 use crate::schema::site_language::dsl::{site_id, site_language};
163 // Clear the current languages
164 delete(site_language.filter(site_id.eq(for_site_id)))
169 let form = SiteLanguageForm {
170 site_id: for_site_id,
173 insert_into(site_language)
175 .get_result::<Self>(conn)
179 CommunityLanguage::limit_languages(conn, instance_id).await?;
188 impl CommunityLanguage {
189 /// Returns true if the given language is one of configured languages for given community
190 pub async fn is_allowed_community_language(
192 for_language_id: Option<LanguageId>,
193 for_community_id: CommunityId,
194 ) -> Result<(), LemmyError> {
195 use crate::schema::community_language::dsl::{community_id, community_language, language_id};
196 let conn = &mut get_conn(pool).await?;
198 if let Some(for_language_id) = for_language_id {
199 let is_allowed = select(exists(
201 .filter(language_id.eq(for_language_id))
202 .filter(community_id.eq(for_community_id)),
210 Err(LemmyError::from_message("language_not_allowed"))
217 /// When site languages are updated, delete all languages of local communities which are not
218 /// also part of site languages. This is because post/comment language is only checked against
219 /// community language, and it shouldnt be possible to post content in languages which are not
220 /// allowed by local site.
221 async fn limit_languages(
222 conn: &mut AsyncPgConnection,
223 for_instance_id: InstanceId,
224 ) -> Result<(), Error> {
227 community_language::dsl as cl,
228 site_language::dsl as sl,
230 let community_languages: Vec<LanguageId> = cl::community_language
231 .left_outer_join(sl::site_language.on(cl::language_id.eq(sl::language_id)))
232 .inner_join(c::community)
233 .filter(c::instance_id.eq(for_instance_id))
234 .filter(sl::language_id.is_null())
235 .select(cl::language_id)
239 for c in community_languages {
240 delete(cl::community_language.filter(cl::language_id.eq(c)))
248 conn: &mut PooledConnection<AsyncPgConnection>,
249 for_community_id: CommunityId,
250 ) -> Result<Vec<LanguageId>, Error> {
251 use crate::schema::community_language::dsl::{community_id, community_language, language_id};
253 .filter(community_id.eq(for_community_id))
262 for_community_id: CommunityId,
263 ) -> Result<Vec<LanguageId>, Error> {
264 let conn = &mut get_conn(pool).await?;
265 let langs = Self::read_raw(conn, for_community_id).await?;
266 convert_read_languages(conn, langs).await
271 mut language_ids: Vec<LanguageId>,
272 for_community_id: CommunityId,
273 ) -> Result<(), Error> {
274 let conn = &mut get_conn(pool).await?;
275 if language_ids.is_empty() {
276 language_ids = SiteLanguage::read_local_raw(pool).await?;
278 let lang_ids = convert_update_languages(conn, language_ids).await?;
280 // No need to update if languages are unchanged
281 let current = CommunityLanguage::read_raw(conn, for_community_id).await?;
282 if current == lang_ids {
289 Box::pin(async move {
290 use crate::schema::community_language::dsl::{community_id, community_language};
291 // Clear the current languages
292 delete(community_language.filter(community_id.eq(for_community_id)))
297 let form = CommunityLanguageForm {
298 community_id: for_community_id,
301 insert_into(community_language)
303 .get_result::<Self>(conn)
313 pub async fn default_post_language(
315 community_id: CommunityId,
316 local_user_id: LocalUserId,
317 ) -> Result<Option<LanguageId>, Error> {
318 use crate::schema::{community_language::dsl as cl, local_user_language::dsl as ul};
319 let conn = &mut get_conn(pool).await?;
320 let mut intersection = ul::local_user_language
321 .inner_join(cl::community_language.on(ul::language_id.eq(cl::language_id)))
322 .filter(ul::local_user_id.eq(local_user_id))
323 .filter(cl::community_id.eq(community_id))
324 .select(cl::language_id)
325 .get_results::<LanguageId>(conn)
328 if intersection.len() == 1 {
329 Ok(intersection.pop())
330 } else if intersection.len() == 2 && intersection.contains(&UNDETERMINED_ID) {
331 intersection.retain(|i| i != &UNDETERMINED_ID);
332 Ok(intersection.pop())
338 /// If no language is given, set all languages
339 async fn convert_update_languages(
340 conn: &mut AsyncPgConnection,
341 language_ids: Vec<LanguageId>,
342 ) -> Result<Vec<LanguageId>, Error> {
343 if language_ids.is_empty() {
345 Language::read_all_conn(conn)
356 /// If all languages are returned, return empty vec instead
357 async fn convert_read_languages(
358 conn: &mut AsyncPgConnection,
359 language_ids: Vec<LanguageId>,
360 ) -> Result<Vec<LanguageId>, Error> {
361 static ALL_LANGUAGES_COUNT: OnceCell<usize> = OnceCell::const_new();
362 let count = ALL_LANGUAGES_COUNT
363 .get_or_init(|| async {
364 use crate::schema::language::dsl::{id, language};
365 let count: i64 = language
369 .expect("read number of languages");
374 if &language_ids.len() == count {
385 impls::actor_language::{
386 convert_read_languages,
387 convert_update_languages,
388 default_post_language,
400 community::{Community, CommunityInsertForm},
402 local_site::{LocalSite, LocalSiteInsertForm},
403 local_user::{LocalUser, LocalUserInsertForm},
404 person::{Person, PersonInsertForm},
405 site::{Site, SiteInsertForm},
408 utils::build_db_pool_for_tests,
410 use serial_test::serial;
412 async fn test_langs1(pool: &DbPool) -> Vec<LanguageId> {
414 Language::read_id_from_code(pool, Some("en"))
418 Language::read_id_from_code(pool, Some("fr"))
422 Language::read_id_from_code(pool, Some("ru"))
428 async fn test_langs2(pool: &DbPool) -> Vec<LanguageId> {
430 Language::read_id_from_code(pool, Some("fi"))
434 Language::read_id_from_code(pool, Some("se"))
441 async fn create_test_site(pool: &DbPool) -> (Site, Instance) {
442 let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
446 let site_form = SiteInsertForm::builder()
447 .name("test site".to_string())
448 .instance_id(inserted_instance.id)
450 let site = Site::create(pool, &site_form).await.unwrap();
452 // Create a local site, since this is necessary for local languages
453 let local_site_form = LocalSiteInsertForm::builder().site_id(site.id).build();
454 LocalSite::create(pool, &local_site_form).await.unwrap();
456 (site, inserted_instance)
461 async fn test_convert_update_languages() {
462 let pool = &build_db_pool_for_tests().await;
464 // call with empty vec, returns all languages
465 let conn = &mut get_conn(pool).await.unwrap();
466 let converted1 = convert_update_languages(conn, vec![]).await.unwrap();
467 assert_eq!(184, converted1.len());
469 // call with nonempty vec, returns same vec
470 let test_langs = test_langs1(pool).await;
471 let converted2 = convert_update_languages(conn, test_langs.clone())
474 assert_eq!(test_langs, converted2);
478 async fn test_convert_read_languages() {
479 use crate::schema::language::dsl::{id, language};
480 let pool = &build_db_pool_for_tests().await;
482 // call with all languages, returns empty vec
483 let conn = &mut get_conn(pool).await.unwrap();
484 let all_langs = language.select(id).get_results(conn).await.unwrap();
485 let converted1: Vec<LanguageId> = convert_read_languages(conn, all_langs).await.unwrap();
486 assert_eq!(0, converted1.len());
488 // call with nonempty vec, returns same vec
489 let test_langs = test_langs1(pool).await;
490 let converted2 = convert_read_languages(conn, test_langs.clone())
493 assert_eq!(test_langs, converted2);
498 async fn test_site_languages() {
499 let pool = &build_db_pool_for_tests().await;
501 let (site, instance) = create_test_site(pool).await;
502 let site_languages1 = SiteLanguage::read_local_raw(pool).await.unwrap();
503 // site is created with all languages
504 assert_eq!(184, site_languages1.len());
506 let test_langs = test_langs1(pool).await;
507 SiteLanguage::update(pool, test_langs.clone(), &site)
511 let site_languages2 = SiteLanguage::read_local_raw(pool).await.unwrap();
512 // after update, site only has new languages
513 assert_eq!(test_langs, site_languages2);
515 Site::delete(pool, site.id).await.unwrap();
516 Instance::delete(pool, instance.id).await.unwrap();
517 LocalSite::delete(pool).await.unwrap();
522 async fn test_user_languages() {
523 let pool = &build_db_pool_for_tests().await;
525 let (site, instance) = create_test_site(pool).await;
526 let test_langs = test_langs1(pool).await;
527 SiteLanguage::update(pool, test_langs.clone(), &site)
531 let person_form = PersonInsertForm::builder()
532 .name("my test person".to_string())
533 .public_key("pubkey".to_string())
534 .instance_id(instance.id)
536 let person = Person::create(pool, &person_form).await.unwrap();
537 let local_user_form = LocalUserInsertForm::builder()
538 .person_id(person.id)
539 .password_encrypted("my_pw".to_string())
542 let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
543 let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
545 // new user should be initialized with site languages
546 assert_eq!(test_langs, local_user_langs1);
548 // update user languages
549 let test_langs2 = test_langs2(pool).await;
550 LocalUserLanguage::update(pool, test_langs2, local_user.id)
553 let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
554 assert_eq!(2, local_user_langs2.len());
556 Person::delete(pool, person.id).await.unwrap();
557 LocalUser::delete(pool, local_user.id).await.unwrap();
558 Site::delete(pool, site.id).await.unwrap();
559 LocalSite::delete(pool).await.unwrap();
560 Instance::delete(pool, instance.id).await.unwrap();
565 async fn test_community_languages() {
566 let pool = &build_db_pool_for_tests().await;
567 let (site, instance) = create_test_site(pool).await;
568 let test_langs = test_langs1(pool).await;
569 SiteLanguage::update(pool, test_langs.clone(), &site)
573 let read_site_langs = SiteLanguage::read(pool, site.id).await.unwrap();
574 assert_eq!(test_langs, read_site_langs);
576 // Test the local ones are the same
577 let read_local_site_langs = SiteLanguage::read_local_raw(pool).await.unwrap();
578 assert_eq!(test_langs, read_local_site_langs);
580 let community_form = CommunityInsertForm::builder()
581 .name("test community".to_string())
582 .title("test community".to_string())
583 .public_key("pubkey".to_string())
584 .instance_id(instance.id)
586 let community = Community::create(pool, &community_form).await.unwrap();
587 let community_langs1 = CommunityLanguage::read(pool, community.id).await.unwrap();
589 // community is initialized with site languages
590 assert_eq!(test_langs, community_langs1);
593 CommunityLanguage::is_allowed_community_language(pool, Some(test_langs[0]), community.id)
595 assert!(allowed_lang1.is_ok());
597 let test_langs2 = test_langs2(pool).await;
599 CommunityLanguage::is_allowed_community_language(pool, Some(test_langs2[0]), community.id)
601 assert!(allowed_lang2.is_err());
603 // limit site languages to en, fi. after this, community languages should be updated to
604 // intersection of old languages (en, fr, ru) and (en, fi), which is only fi.
605 SiteLanguage::update(pool, vec![test_langs[0], test_langs2[0]], &site)
608 let community_langs2 = CommunityLanguage::read(pool, community.id).await.unwrap();
609 assert_eq!(vec![test_langs[0]], community_langs2);
611 // update community languages to different ones
612 CommunityLanguage::update(pool, test_langs2.clone(), community.id)
615 let community_langs3 = CommunityLanguage::read(pool, community.id).await.unwrap();
616 assert_eq!(test_langs2, community_langs3);
618 Community::delete(pool, community.id).await.unwrap();
619 Site::delete(pool, site.id).await.unwrap();
620 LocalSite::delete(pool).await.unwrap();
621 Instance::delete(pool, instance.id).await.unwrap();
626 async fn test_default_post_language() {
627 let pool = &build_db_pool_for_tests().await;
628 let (site, instance) = create_test_site(pool).await;
629 let mut test_langs = test_langs1(pool).await;
630 test_langs.push(UNDETERMINED_ID);
631 let test_langs2 = test_langs2(pool).await;
633 let community_form = CommunityInsertForm::builder()
634 .name("test community".to_string())
635 .title("test community".to_string())
636 .public_key("pubkey".to_string())
637 .instance_id(instance.id)
639 let community = Community::create(pool, &community_form).await.unwrap();
640 CommunityLanguage::update(pool, test_langs, community.id)
644 let person_form = PersonInsertForm::builder()
645 .name("my test person".to_string())
646 .public_key("pubkey".to_string())
647 .instance_id(instance.id)
649 let person = Person::create(pool, &person_form).await.unwrap();
650 let local_user_form = LocalUserInsertForm::builder()
651 .person_id(person.id)
652 .password_encrypted("my_pw".to_string())
654 let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
655 LocalUserLanguage::update(pool, test_langs2, local_user.id)
659 // no overlap in user/community languages, so no default language for post
660 let def1 = default_post_language(pool, community.id, local_user.id)
663 assert_eq!(None, def1);
665 let ru = Language::read_id_from_code(pool, Some("ru"))
669 let test_langs3 = vec![
671 Language::read_id_from_code(pool, Some("fi"))
675 Language::read_id_from_code(pool, Some("se"))
681 LocalUserLanguage::update(pool, test_langs3, local_user.id)
685 // this time, both have ru as common lang
686 let def2 = default_post_language(pool, community.id, local_user.id)
689 assert_eq!(Some(ru), def2);
691 Person::delete(pool, person.id).await.unwrap();
692 Community::delete(pool, community.id).await.unwrap();
693 LocalUser::delete(pool, local_user.id).await.unwrap();
694 Site::delete(pool, site.id).await.unwrap();
695 LocalSite::delete(pool).await.unwrap();
696 Instance::delete(pool, instance.id).await.unwrap();