]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/actor_language.rs
Various pedantic clippy fixes (#2568)
[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::{AsyncPgConnection, RunQueryDsl};
29 use lemmy_utils::error::LemmyError;
30 use tokio::sync::OnceCell;
31
32 impl LocalUserLanguage {
33   pub async fn read(
34     pool: &DbPool,
35     for_local_user_id: LocalUserId,
36   ) -> Result<Vec<LanguageId>, Error> {
37     use crate::schema::local_user_language::dsl::{
38       language_id,
39       local_user_id,
40       local_user_language,
41     };
42     let conn = &mut get_conn(pool).await?;
43
44     conn
45       .build_transaction()
46       .run(|conn| {
47         Box::pin(async move {
48           let langs = local_user_language
49             .filter(local_user_id.eq(for_local_user_id))
50             .order(language_id)
51             .select(language_id)
52             .get_results(conn)
53             .await?;
54           convert_read_languages(conn, langs).await
55         }) as _
56       })
57       .await
58   }
59
60   /// Update the user's languages.
61   ///
62   /// If no language_id vector is given, it will show all languages
63   pub async fn update(
64     pool: &DbPool,
65     language_ids: Vec<LanguageId>,
66     for_local_user_id: LocalUserId,
67   ) -> Result<(), Error> {
68     let conn = &mut get_conn(pool).await?;
69
70     conn
71       .build_transaction()
72       .run(|conn| {
73         Box::pin(async move {
74           use crate::schema::local_user_language::dsl::{local_user_id, local_user_language};
75           // Clear the current user languages
76           delete(local_user_language.filter(local_user_id.eq(for_local_user_id)))
77             .execute(conn)
78             .await?;
79
80           let lang_ids = convert_update_languages(conn, language_ids).await?;
81           for l in lang_ids {
82             let form = LocalUserLanguageForm {
83               local_user_id: for_local_user_id,
84               language_id: l,
85             };
86             insert_into(local_user_language)
87               .values(form)
88               .get_result::<Self>(conn)
89               .await?;
90           }
91           Ok(())
92         }) as _
93       })
94       .await
95   }
96 }
97
98 impl SiteLanguage {
99   pub async fn read_local(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
100     let conn = &mut get_conn(pool).await?;
101     site::table
102       .inner_join(local_site::table)
103       .inner_join(site_language::table)
104       .order(site_language::id)
105       .select(site_language::language_id)
106       .load(conn)
107       .await
108   }
109
110   pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
111     let conn = &mut get_conn(pool).await?;
112
113     let langs = site_language::table
114       .filter(site_language::site_id.eq(for_site_id))
115       .order(site_language::language_id)
116       .select(site_language::language_id)
117       .load(conn)
118       .await?;
119     convert_read_languages(conn, langs).await
120   }
121
122   pub async fn update(
123     pool: &DbPool,
124     language_ids: Vec<LanguageId>,
125     site: &Site,
126   ) -> Result<(), Error> {
127     let conn = &mut get_conn(pool).await?;
128     let for_site_id = site.id;
129     let instance_id = site.instance_id;
130
131     conn
132       .build_transaction()
133       .run(|conn| {
134         Box::pin(async move {
135           use crate::schema::site_language::dsl::{site_id, site_language};
136
137           // Clear the current languages
138           delete(site_language.filter(site_id.eq(for_site_id)))
139             .execute(conn)
140             .await?;
141
142           let lang_ids = convert_update_languages(conn, language_ids).await?;
143           for l in lang_ids {
144             let form = SiteLanguageForm {
145               site_id: for_site_id,
146               language_id: l,
147             };
148             insert_into(site_language)
149               .values(form)
150               .get_result::<Self>(conn)
151               .await?;
152           }
153
154           CommunityLanguage::limit_languages(conn, instance_id).await?;
155
156           Ok(())
157         }) as _
158       })
159       .await
160   }
161 }
162
163 impl CommunityLanguage {
164   /// Returns true if the given language is one of configured languages for given community
165   pub async fn is_allowed_community_language(
166     pool: &DbPool,
167     for_language_id: Option<LanguageId>,
168     for_community_id: CommunityId,
169   ) -> Result<(), LemmyError> {
170     use crate::schema::community_language::dsl::{community_id, community_language, language_id};
171     let conn = &mut get_conn(pool).await?;
172
173     if let Some(for_language_id) = for_language_id {
174       let is_allowed = select(exists(
175         community_language
176           .filter(language_id.eq(for_language_id))
177           .filter(community_id.eq(for_community_id)),
178       ))
179       .get_result(conn)
180       .await?;
181
182       if is_allowed {
183         Ok(())
184       } else {
185         Err(LemmyError::from_message("language_not_allowed"))
186       }
187     } else {
188       Ok(())
189     }
190   }
191
192   /// When site languages are updated, delete all languages of local communities which are not
193   /// also part of site languages. This is because post/comment language is only checked against
194   /// community language, and it shouldnt be possible to post content in languages which are not
195   /// allowed by local site.
196   async fn limit_languages(
197     conn: &mut AsyncPgConnection,
198     for_instance_id: InstanceId,
199   ) -> Result<(), Error> {
200     use crate::schema::{
201       community::dsl as c,
202       community_language::dsl as cl,
203       site_language::dsl as sl,
204     };
205     let community_languages: Vec<LanguageId> = cl::community_language
206       .left_outer_join(sl::site_language.on(cl::language_id.eq(sl::language_id)))
207       .inner_join(c::community)
208       .filter(c::instance_id.eq(for_instance_id))
209       .filter(sl::language_id.is_null())
210       .select(cl::language_id)
211       .get_results(conn)
212       .await?;
213
214     for c in community_languages {
215       delete(cl::community_language.filter(cl::language_id.eq(c)))
216         .execute(conn)
217         .await?;
218     }
219     Ok(())
220   }
221
222   pub async fn read(
223     pool: &DbPool,
224     for_community_id: CommunityId,
225   ) -> Result<Vec<LanguageId>, Error> {
226     use crate::schema::community_language::dsl::{community_id, community_language, language_id};
227     let conn = &mut get_conn(pool).await?;
228
229     let langs = community_language
230       .filter(community_id.eq(for_community_id))
231       .order(language_id)
232       .select(language_id)
233       .get_results(conn)
234       .await?;
235     convert_read_languages(conn, langs).await
236   }
237
238   pub async fn update(
239     pool: &DbPool,
240     mut language_ids: Vec<LanguageId>,
241     for_community_id: CommunityId,
242   ) -> Result<(), Error> {
243     let conn = &mut get_conn(pool).await?;
244
245     if language_ids.is_empty() {
246       language_ids = SiteLanguage::read_local(pool).await?;
247     }
248
249     conn
250       .build_transaction()
251       .run(|conn| {
252         Box::pin(async move {
253           use crate::schema::community_language::dsl::{community_id, community_language};
254           // Clear the current languages
255           delete(community_language.filter(community_id.eq(for_community_id)))
256             .execute(conn)
257             .await?;
258
259           for l in language_ids {
260             let form = CommunityLanguageForm {
261               community_id: for_community_id,
262               language_id: l,
263             };
264             insert_into(community_language)
265               .values(form)
266               .get_result::<Self>(conn)
267               .await?;
268           }
269           Ok(())
270         }) as _
271       })
272       .await
273   }
274 }
275
276 pub async fn default_post_language(
277   pool: &DbPool,
278   community_id: CommunityId,
279   local_user_id: LocalUserId,
280 ) -> Result<Option<LanguageId>, Error> {
281   use crate::schema::{community_language::dsl as cl, local_user_language::dsl as ul};
282   let conn = &mut get_conn(pool).await?;
283   let intersection = ul::local_user_language
284     .inner_join(cl::community_language.on(ul::language_id.eq(cl::language_id)))
285     .filter(ul::local_user_id.eq(local_user_id))
286     .filter(cl::community_id.eq(community_id))
287     .select(cl::language_id)
288     .get_results::<LanguageId>(conn)
289     .await?;
290
291   if intersection.len() == 1 {
292     Ok(Some(intersection[0]))
293   } else {
294     Ok(None)
295   }
296 }
297
298 /// If no language is given, set all languages
299 async fn convert_update_languages(
300   conn: &mut AsyncPgConnection,
301   language_ids: Vec<LanguageId>,
302 ) -> Result<Vec<LanguageId>, Error> {
303   if language_ids.is_empty() {
304     Ok(
305       Language::read_all_conn(conn)
306         .await?
307         .into_iter()
308         .map(|l| l.id)
309         .collect(),
310     )
311   } else {
312     Ok(language_ids)
313   }
314 }
315
316 /// If all languages are returned, return empty vec instead
317 async fn convert_read_languages(
318   conn: &mut AsyncPgConnection,
319   language_ids: Vec<LanguageId>,
320 ) -> Result<Vec<LanguageId>, Error> {
321   static ALL_LANGUAGES_COUNT: OnceCell<usize> = OnceCell::const_new();
322   let count = ALL_LANGUAGES_COUNT
323     .get_or_init(|| async {
324       use crate::schema::language::dsl::{id, language};
325       let count: i64 = language
326         .select(count(id))
327         .first(conn)
328         .await
329         .expect("read number of languages");
330       count as usize
331     })
332     .await;
333
334   if &language_ids.len() == count {
335     Ok(vec![])
336   } else {
337     Ok(language_ids)
338   }
339 }
340
341 #[cfg(test)]
342 mod tests {
343   use crate::{
344     impls::actor_language::{
345       convert_read_languages,
346       convert_update_languages,
347       default_post_language,
348       get_conn,
349       CommunityLanguage,
350       DbPool,
351       Language,
352       LanguageId,
353       LocalUserLanguage,
354       QueryDsl,
355       RunQueryDsl,
356       SiteLanguage,
357     },
358     source::{
359       community::{Community, CommunityInsertForm},
360       instance::Instance,
361       local_site::{LocalSite, LocalSiteInsertForm},
362       local_user::{LocalUser, LocalUserInsertForm},
363       person::{Person, PersonInsertForm},
364       site::{Site, SiteInsertForm},
365     },
366     traits::Crud,
367     utils::build_db_pool_for_tests,
368   };
369   use serial_test::serial;
370
371   async fn test_langs1(pool: &DbPool) -> Vec<LanguageId> {
372     vec![
373       Language::read_id_from_code(pool, "en").await.unwrap(),
374       Language::read_id_from_code(pool, "fr").await.unwrap(),
375       Language::read_id_from_code(pool, "ru").await.unwrap(),
376     ]
377   }
378   async fn test_langs2(pool: &DbPool) -> Vec<LanguageId> {
379     vec![
380       Language::read_id_from_code(pool, "fi").await.unwrap(),
381       Language::read_id_from_code(pool, "se").await.unwrap(),
382     ]
383   }
384
385   async fn create_test_site(pool: &DbPool) -> (Site, Instance) {
386     let inserted_instance = Instance::create(pool, "my_domain.tld").await.unwrap();
387
388     let site_form = SiteInsertForm::builder()
389       .name("test site".to_string())
390       .instance_id(inserted_instance.id)
391       .build();
392     let site = Site::create(pool, &site_form).await.unwrap();
393
394     // Create a local site, since this is necessary for local languages
395     let local_site_form = LocalSiteInsertForm::builder().site_id(site.id).build();
396     LocalSite::create(pool, &local_site_form).await.unwrap();
397
398     (site, inserted_instance)
399   }
400
401   #[tokio::test]
402   #[serial]
403   async fn test_convert_update_languages() {
404     let pool = &build_db_pool_for_tests().await;
405
406     // call with empty vec, returns all languages
407     let conn = &mut get_conn(pool).await.unwrap();
408     let converted1 = convert_update_languages(conn, vec![]).await.unwrap();
409     assert_eq!(184, converted1.len());
410
411     // call with nonempty vec, returns same vec
412     let test_langs = test_langs1(pool).await;
413     let converted2 = convert_update_languages(conn, test_langs.clone())
414       .await
415       .unwrap();
416     assert_eq!(test_langs, converted2);
417   }
418   #[tokio::test]
419   #[serial]
420   async fn test_convert_read_languages() {
421     use crate::schema::language::dsl::{id, language};
422     let pool = &build_db_pool_for_tests().await;
423
424     // call with all languages, returns empty vec
425     let conn = &mut get_conn(pool).await.unwrap();
426     let all_langs = language.select(id).get_results(conn).await.unwrap();
427     let converted1: Vec<LanguageId> = convert_read_languages(conn, all_langs).await.unwrap();
428     assert_eq!(0, converted1.len());
429
430     // call with nonempty vec, returns same vec
431     let test_langs = test_langs1(pool).await;
432     let converted2 = convert_read_languages(conn, test_langs.clone())
433       .await
434       .unwrap();
435     assert_eq!(test_langs, converted2);
436   }
437
438   #[tokio::test]
439   #[serial]
440   async fn test_site_languages() {
441     let pool = &build_db_pool_for_tests().await;
442
443     let (site, instance) = create_test_site(pool).await;
444     let site_languages1 = SiteLanguage::read_local(pool).await.unwrap();
445     // site is created with all languages
446     assert_eq!(184, site_languages1.len());
447
448     let test_langs = test_langs1(pool).await;
449     SiteLanguage::update(pool, test_langs.clone(), &site)
450       .await
451       .unwrap();
452
453     let site_languages2 = SiteLanguage::read_local(pool).await.unwrap();
454     // after update, site only has new languages
455     assert_eq!(test_langs, site_languages2);
456
457     Site::delete(pool, site.id).await.unwrap();
458     Instance::delete(pool, instance.id).await.unwrap();
459     LocalSite::delete(pool).await.unwrap();
460   }
461
462   #[tokio::test]
463   #[serial]
464   async fn test_user_languages() {
465     let pool = &build_db_pool_for_tests().await;
466
467     let (site, instance) = create_test_site(pool).await;
468     let test_langs = test_langs1(pool).await;
469     SiteLanguage::update(pool, test_langs.clone(), &site)
470       .await
471       .unwrap();
472
473     let person_form = PersonInsertForm::builder()
474       .name("my test person".to_string())
475       .public_key("pubkey".to_string())
476       .instance_id(instance.id)
477       .build();
478     let person = Person::create(pool, &person_form).await.unwrap();
479     let local_user_form = LocalUserInsertForm::builder()
480       .person_id(person.id)
481       .password_encrypted("my_pw".to_string())
482       .build();
483
484     let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
485     let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
486
487     // new user should be initialized with site languages
488     assert_eq!(test_langs, local_user_langs1);
489
490     // update user languages
491     let test_langs2 = test_langs2(pool).await;
492     LocalUserLanguage::update(pool, test_langs2, local_user.id)
493       .await
494       .unwrap();
495     let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
496     assert_eq!(2, local_user_langs2.len());
497
498     Person::delete(pool, person.id).await.unwrap();
499     LocalUser::delete(pool, local_user.id).await.unwrap();
500     Site::delete(pool, site.id).await.unwrap();
501     LocalSite::delete(pool).await.unwrap();
502     Instance::delete(pool, instance.id).await.unwrap();
503   }
504
505   #[tokio::test]
506   #[serial]
507   async fn test_community_languages() {
508     let pool = &build_db_pool_for_tests().await;
509     let (site, instance) = create_test_site(pool).await;
510     let test_langs = test_langs1(pool).await;
511     SiteLanguage::update(pool, test_langs.clone(), &site)
512       .await
513       .unwrap();
514
515     let read_site_langs = SiteLanguage::read(pool, site.id).await.unwrap();
516     assert_eq!(test_langs, read_site_langs);
517
518     // Test the local ones are the same
519     let read_local_site_langs = SiteLanguage::read_local(pool).await.unwrap();
520     assert_eq!(test_langs, read_local_site_langs);
521
522     let community_form = CommunityInsertForm::builder()
523       .name("test community".to_string())
524       .title("test community".to_string())
525       .public_key("pubkey".to_string())
526       .instance_id(instance.id)
527       .build();
528     let community = Community::create(pool, &community_form).await.unwrap();
529     let community_langs1 = CommunityLanguage::read(pool, community.id).await.unwrap();
530
531     // community is initialized with site languages
532     assert_eq!(test_langs, community_langs1);
533
534     let allowed_lang1 =
535       CommunityLanguage::is_allowed_community_language(pool, Some(test_langs[0]), community.id)
536         .await;
537     assert!(allowed_lang1.is_ok());
538
539     let test_langs2 = test_langs2(pool).await;
540     let allowed_lang2 =
541       CommunityLanguage::is_allowed_community_language(pool, Some(test_langs2[0]), community.id)
542         .await;
543     assert!(allowed_lang2.is_err());
544
545     // limit site languages to en, fi. after this, community languages should be updated to
546     // intersection of old languages (en, fr, ru) and (en, fi), which is only fi.
547     SiteLanguage::update(pool, vec![test_langs[0], test_langs2[0]], &site)
548       .await
549       .unwrap();
550     let community_langs2 = CommunityLanguage::read(pool, community.id).await.unwrap();
551     assert_eq!(vec![test_langs[0]], community_langs2);
552
553     // update community languages to different ones
554     CommunityLanguage::update(pool, test_langs2.clone(), community.id)
555       .await
556       .unwrap();
557     let community_langs3 = CommunityLanguage::read(pool, community.id).await.unwrap();
558     assert_eq!(test_langs2, community_langs3);
559
560     Community::delete(pool, community.id).await.unwrap();
561     Site::delete(pool, site.id).await.unwrap();
562     LocalSite::delete(pool).await.unwrap();
563     Instance::delete(pool, instance.id).await.unwrap();
564   }
565
566   #[tokio::test]
567   #[serial]
568   async fn test_default_post_language() {
569     let pool = &build_db_pool_for_tests().await;
570     let (site, instance) = create_test_site(pool).await;
571     let test_langs = test_langs1(pool).await;
572     let test_langs2 = test_langs2(pool).await;
573
574     let community_form = CommunityInsertForm::builder()
575       .name("test community".to_string())
576       .title("test community".to_string())
577       .public_key("pubkey".to_string())
578       .instance_id(instance.id)
579       .build();
580     let community = Community::create(pool, &community_form).await.unwrap();
581     CommunityLanguage::update(pool, test_langs, community.id)
582       .await
583       .unwrap();
584
585     let person_form = PersonInsertForm::builder()
586       .name("my test person".to_string())
587       .public_key("pubkey".to_string())
588       .instance_id(instance.id)
589       .build();
590     let person = Person::create(pool, &person_form).await.unwrap();
591     let local_user_form = LocalUserInsertForm::builder()
592       .person_id(person.id)
593       .password_encrypted("my_pw".to_string())
594       .build();
595     let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
596     LocalUserLanguage::update(pool, test_langs2, local_user.id)
597       .await
598       .unwrap();
599
600     // no overlap in user/community languages, so no default language for post
601     let def1 = default_post_language(pool, community.id, local_user.id)
602       .await
603       .unwrap();
604     assert_eq!(None, def1);
605
606     let ru = Language::read_id_from_code(pool, "ru").await.unwrap();
607     let test_langs3 = vec![
608       ru,
609       Language::read_id_from_code(pool, "fi").await.unwrap(),
610       Language::read_id_from_code(pool, "se").await.unwrap(),
611     ];
612     LocalUserLanguage::update(pool, test_langs3, local_user.id)
613       .await
614       .unwrap();
615
616     // this time, both have ru as common lang
617     let def2 = default_post_language(pool, community.id, local_user.id)
618       .await
619       .unwrap();
620     assert_eq!(Some(ru), def2);
621
622     Person::delete(pool, person.id).await.unwrap();
623     Community::delete(pool, community.id).await.unwrap();
624     LocalUser::delete(pool, local_user.id).await.unwrap();
625     Site::delete(pool, site.id).await.unwrap();
626     LocalSite::delete(pool).await.unwrap();
627     Instance::delete(pool, instance.id).await.unwrap();
628   }
629 }