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