]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/actor_language.rs
624eaf54f8daee425797db0d4fe6f6c1c34505d1
[lemmy.git] / crates / db_schema / src / impls / actor_language.rs
1 use crate::{
2   diesel::JoinOnDsl,
3   newtypes::{CommunityId, InstanceId, LanguageId, LocalUserId, SiteId},
4   schema::{local_site, site, site_language},
5   source::{
6     actor_language::{
7       CommunityLanguage,
8       CommunityLanguageForm,
9       LocalUserLanguage,
10       LocalUserLanguageForm,
11       SiteLanguage,
12       SiteLanguageForm,
13     },
14     language::Language,
15     site::Site,
16   },
17   utils::{get_conn, DbPool},
18 };
19 use diesel::{
20   delete,
21   dsl::{count, exists},
22   insert_into,
23   result::Error,
24   select,
25   ExpressionMethods,
26   QueryDsl,
27 };
28 use diesel_async::{
29   pooled_connection::deadpool::Object as PooledConnection,
30   AsyncPgConnection,
31   RunQueryDsl,
32 };
33 use lemmy_utils::error::LemmyError;
34 use tokio::sync::OnceCell;
35
36 pub const UNDETERMINED_ID: LanguageId = LanguageId(0);
37
38 impl LocalUserLanguage {
39   pub async fn read(
40     pool: &DbPool,
41     for_local_user_id: LocalUserId,
42   ) -> Result<Vec<LanguageId>, Error> {
43     use crate::schema::local_user_language::dsl::{
44       language_id,
45       local_user_id,
46       local_user_language,
47     };
48     let conn = &mut get_conn(pool).await?;
49
50     conn
51       .build_transaction()
52       .run(|conn| {
53         Box::pin(async move {
54           let langs = local_user_language
55             .filter(local_user_id.eq(for_local_user_id))
56             .order(language_id)
57             .select(language_id)
58             .get_results(conn)
59             .await?;
60           convert_read_languages(conn, langs).await
61         }) as _
62       })
63       .await
64   }
65
66   /// Update the user's languages.
67   ///
68   /// If no language_id vector is given, it will show all languages
69   pub async fn update(
70     pool: &DbPool,
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?;
76
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 {
80       return Ok(());
81     }
82
83     conn
84       .build_transaction()
85       .run(|conn| {
86         Box::pin(async move {
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)))
90             .execute(conn)
91             .await?;
92
93           for l in lang_ids {
94             let form = LocalUserLanguageForm {
95               local_user_id: for_local_user_id,
96               language_id: l,
97             };
98             insert_into(local_user_language)
99               .values(form)
100               .get_result::<Self>(conn)
101               .await?;
102           }
103           Ok(())
104         }) as _
105       })
106       .await
107   }
108 }
109
110 impl SiteLanguage {
111   pub async fn read_local_raw(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
112     let conn = &mut get_conn(pool).await?;
113     site::table
114       .inner_join(local_site::table)
115       .inner_join(site_language::table)
116       .order(site_language::id)
117       .select(site_language::language_id)
118       .load(conn)
119       .await
120   }
121
122   async fn read_raw(
123     conn: &mut PooledConnection<AsyncPgConnection>,
124     for_site_id: SiteId,
125   ) -> Result<Vec<LanguageId>, Error> {
126     site_language::table
127       .filter(site_language::site_id.eq(for_site_id))
128       .order(site_language::language_id)
129       .select(site_language::language_id)
130       .load(conn)
131       .await
132   }
133
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?;
137
138     convert_read_languages(conn, langs).await
139   }
140
141   pub async fn update(
142     pool: &DbPool,
143     language_ids: Vec<LanguageId>,
144     site: &Site,
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?;
150
151     // No need to update if languages are unchanged
152     let current = SiteLanguage::read(pool, site.id).await?;
153     if current == lang_ids {
154       return Ok(());
155     }
156
157     conn
158       .build_transaction()
159       .run(|conn| {
160         Box::pin(async move {
161           use crate::schema::site_language::dsl::{site_id, site_language};
162
163           // Clear the current languages
164           delete(site_language.filter(site_id.eq(for_site_id)))
165             .execute(conn)
166             .await?;
167
168           for l in lang_ids {
169             let form = SiteLanguageForm {
170               site_id: for_site_id,
171               language_id: l,
172             };
173             insert_into(site_language)
174               .values(form)
175               .get_result::<Self>(conn)
176               .await?;
177           }
178
179           CommunityLanguage::limit_languages(conn, instance_id).await?;
180
181           Ok(())
182         }) as _
183       })
184       .await
185   }
186 }
187
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(
191     pool: &DbPool,
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?;
197
198     if let Some(for_language_id) = for_language_id {
199       let is_allowed = select(exists(
200         community_language
201           .filter(language_id.eq(for_language_id))
202           .filter(community_id.eq(for_community_id)),
203       ))
204       .get_result(conn)
205       .await?;
206
207       if is_allowed {
208         Ok(())
209       } else {
210         Err(LemmyError::from_message("language_not_allowed"))
211       }
212     } else {
213       Ok(())
214     }
215   }
216
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> {
225     use crate::schema::{
226       community::dsl as c,
227       community_language::dsl as cl,
228       site_language::dsl as sl,
229     };
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)
236       .get_results(conn)
237       .await?;
238
239     for c in community_languages {
240       delete(cl::community_language.filter(cl::language_id.eq(c)))
241         .execute(conn)
242         .await?;
243     }
244     Ok(())
245   }
246
247   async fn read_raw(
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};
252     community_language
253       .filter(community_id.eq(for_community_id))
254       .order(language_id)
255       .select(language_id)
256       .get_results(conn)
257       .await
258   }
259
260   pub async fn read(
261     pool: &DbPool,
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
267   }
268
269   pub async fn update(
270     pool: &DbPool,
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?;
277     }
278     let lang_ids = convert_update_languages(conn, language_ids).await?;
279
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 {
283       return Ok(());
284     }
285
286     conn
287       .build_transaction()
288       .run(|conn| {
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)))
293             .execute(conn)
294             .await?;
295
296           for l in lang_ids {
297             let form = CommunityLanguageForm {
298               community_id: for_community_id,
299               language_id: l,
300             };
301             insert_into(community_language)
302               .values(form)
303               .get_result::<Self>(conn)
304               .await?;
305           }
306           Ok(())
307         }) as _
308       })
309       .await
310   }
311 }
312
313 pub async fn default_post_language(
314   pool: &DbPool,
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)
326     .await?;
327
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())
333   } else {
334     Ok(None)
335   }
336 }
337
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() {
344     Ok(
345       Language::read_all_conn(conn)
346         .await?
347         .into_iter()
348         .map(|l| l.id)
349         .collect(),
350     )
351   } else {
352     Ok(language_ids)
353   }
354 }
355
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
366         .select(count(id))
367         .first(conn)
368         .await
369         .expect("read number of languages");
370       count as usize
371     })
372     .await;
373
374   if &language_ids.len() == count {
375     Ok(vec![])
376   } else {
377     Ok(language_ids)
378   }
379 }
380
381 #[cfg(test)]
382 mod tests {
383   use super::*;
384   use crate::{
385     impls::actor_language::{
386       convert_read_languages,
387       convert_update_languages,
388       default_post_language,
389       get_conn,
390       CommunityLanguage,
391       DbPool,
392       Language,
393       LanguageId,
394       LocalUserLanguage,
395       QueryDsl,
396       RunQueryDsl,
397       SiteLanguage,
398     },
399     source::{
400       community::{Community, CommunityInsertForm},
401       instance::Instance,
402       local_site::{LocalSite, LocalSiteInsertForm},
403       local_user::{LocalUser, LocalUserInsertForm},
404       person::{Person, PersonInsertForm},
405       site::{Site, SiteInsertForm},
406     },
407     traits::Crud,
408     utils::build_db_pool_for_tests,
409   };
410   use serial_test::serial;
411
412   async fn test_langs1(pool: &DbPool) -> Vec<LanguageId> {
413     vec![
414       Language::read_id_from_code(pool, Some("en"))
415         .await
416         .unwrap()
417         .unwrap(),
418       Language::read_id_from_code(pool, Some("fr"))
419         .await
420         .unwrap()
421         .unwrap(),
422       Language::read_id_from_code(pool, Some("ru"))
423         .await
424         .unwrap()
425         .unwrap(),
426     ]
427   }
428   async fn test_langs2(pool: &DbPool) -> Vec<LanguageId> {
429     vec![
430       Language::read_id_from_code(pool, Some("fi"))
431         .await
432         .unwrap()
433         .unwrap(),
434       Language::read_id_from_code(pool, Some("se"))
435         .await
436         .unwrap()
437         .unwrap(),
438     ]
439   }
440
441   async fn create_test_site(pool: &DbPool) -> (Site, Instance) {
442     let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
443       .await
444       .unwrap();
445
446     let site_form = SiteInsertForm::builder()
447       .name("test site".to_string())
448       .instance_id(inserted_instance.id)
449       .build();
450     let site = Site::create(pool, &site_form).await.unwrap();
451
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();
455
456     (site, inserted_instance)
457   }
458
459   #[tokio::test]
460   #[serial]
461   async fn test_convert_update_languages() {
462     let pool = &build_db_pool_for_tests().await;
463
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());
468
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())
472       .await
473       .unwrap();
474     assert_eq!(test_langs, converted2);
475   }
476   #[tokio::test]
477   #[serial]
478   async fn test_convert_read_languages() {
479     use crate::schema::language::dsl::{id, language};
480     let pool = &build_db_pool_for_tests().await;
481
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());
487
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())
491       .await
492       .unwrap();
493     assert_eq!(test_langs, converted2);
494   }
495
496   #[tokio::test]
497   #[serial]
498   async fn test_site_languages() {
499     let pool = &build_db_pool_for_tests().await;
500
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());
505
506     let test_langs = test_langs1(pool).await;
507     SiteLanguage::update(pool, test_langs.clone(), &site)
508       .await
509       .unwrap();
510
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);
514
515     Site::delete(pool, site.id).await.unwrap();
516     Instance::delete(pool, instance.id).await.unwrap();
517     LocalSite::delete(pool).await.unwrap();
518   }
519
520   #[tokio::test]
521   #[serial]
522   async fn test_user_languages() {
523     let pool = &build_db_pool_for_tests().await;
524
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)
528       .await
529       .unwrap();
530
531     let person_form = PersonInsertForm::builder()
532       .name("my test person".to_string())
533       .public_key("pubkey".to_string())
534       .instance_id(instance.id)
535       .build();
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())
540       .build();
541
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();
544
545     // new user should be initialized with site languages
546     assert_eq!(test_langs, local_user_langs1);
547
548     // update user languages
549     let test_langs2 = test_langs2(pool).await;
550     LocalUserLanguage::update(pool, test_langs2, local_user.id)
551       .await
552       .unwrap();
553     let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
554     assert_eq!(2, local_user_langs2.len());
555
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();
561   }
562
563   #[tokio::test]
564   #[serial]
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)
570       .await
571       .unwrap();
572
573     let read_site_langs = SiteLanguage::read(pool, site.id).await.unwrap();
574     assert_eq!(test_langs, read_site_langs);
575
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);
579
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)
585       .build();
586     let community = Community::create(pool, &community_form).await.unwrap();
587     let community_langs1 = CommunityLanguage::read(pool, community.id).await.unwrap();
588
589     // community is initialized with site languages
590     assert_eq!(test_langs, community_langs1);
591
592     let allowed_lang1 =
593       CommunityLanguage::is_allowed_community_language(pool, Some(test_langs[0]), community.id)
594         .await;
595     assert!(allowed_lang1.is_ok());
596
597     let test_langs2 = test_langs2(pool).await;
598     let allowed_lang2 =
599       CommunityLanguage::is_allowed_community_language(pool, Some(test_langs2[0]), community.id)
600         .await;
601     assert!(allowed_lang2.is_err());
602
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)
606       .await
607       .unwrap();
608     let community_langs2 = CommunityLanguage::read(pool, community.id).await.unwrap();
609     assert_eq!(vec![test_langs[0]], community_langs2);
610
611     // update community languages to different ones
612     CommunityLanguage::update(pool, test_langs2.clone(), community.id)
613       .await
614       .unwrap();
615     let community_langs3 = CommunityLanguage::read(pool, community.id).await.unwrap();
616     assert_eq!(test_langs2, community_langs3);
617
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();
622   }
623
624   #[tokio::test]
625   #[serial]
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;
632
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)
638       .build();
639     let community = Community::create(pool, &community_form).await.unwrap();
640     CommunityLanguage::update(pool, test_langs, community.id)
641       .await
642       .unwrap();
643
644     let person_form = PersonInsertForm::builder()
645       .name("my test person".to_string())
646       .public_key("pubkey".to_string())
647       .instance_id(instance.id)
648       .build();
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())
653       .build();
654     let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
655     LocalUserLanguage::update(pool, test_langs2, local_user.id)
656       .await
657       .unwrap();
658
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)
661       .await
662       .unwrap();
663     assert_eq!(None, def1);
664
665     let ru = Language::read_id_from_code(pool, Some("ru"))
666       .await
667       .unwrap()
668       .unwrap();
669     let test_langs3 = vec![
670       ru,
671       Language::read_id_from_code(pool, Some("fi"))
672         .await
673         .unwrap()
674         .unwrap(),
675       Language::read_id_from_code(pool, Some("se"))
676         .await
677         .unwrap()
678         .unwrap(),
679       UNDETERMINED_ID,
680     ];
681     LocalUserLanguage::update(pool, test_langs3, local_user.id)
682       .await
683       .unwrap();
684
685     // this time, both have ru as common lang
686     let def2 = default_post_language(pool, community.id, local_user.id)
687       .await
688       .unwrap();
689     assert_eq!(Some(ru), def2);
690
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();
697   }
698 }