]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/actor_language.rs
Dont upsert Instance row every apub fetch (#2771)
[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 pub const UNDETERMINED_ID: LanguageId = LanguageId(0);
33
34 impl LocalUserLanguage {
35   pub async fn read(
36     pool: &DbPool,
37     for_local_user_id: LocalUserId,
38   ) -> Result<Vec<LanguageId>, Error> {
39     use crate::schema::local_user_language::dsl::{
40       language_id,
41       local_user_id,
42       local_user_language,
43     };
44     let conn = &mut get_conn(pool).await?;
45
46     conn
47       .build_transaction()
48       .run(|conn| {
49         Box::pin(async move {
50           let langs = local_user_language
51             .filter(local_user_id.eq(for_local_user_id))
52             .order(language_id)
53             .select(language_id)
54             .get_results(conn)
55             .await?;
56           convert_read_languages(conn, langs).await
57         }) as _
58       })
59       .await
60   }
61
62   /// Update the user's languages.
63   ///
64   /// If no language_id vector is given, it will show all languages
65   pub async fn update(
66     pool: &DbPool,
67     language_ids: Vec<LanguageId>,
68     for_local_user_id: LocalUserId,
69   ) -> Result<(), Error> {
70     let conn = &mut get_conn(pool).await?;
71
72     conn
73       .build_transaction()
74       .run(|conn| {
75         Box::pin(async move {
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)))
79             .execute(conn)
80             .await?;
81
82           let lang_ids = convert_update_languages(conn, language_ids).await?;
83           for l in lang_ids {
84             let form = LocalUserLanguageForm {
85               local_user_id: for_local_user_id,
86               language_id: l,
87             };
88             insert_into(local_user_language)
89               .values(form)
90               .get_result::<Self>(conn)
91               .await?;
92           }
93           Ok(())
94         }) as _
95       })
96       .await
97   }
98 }
99
100 impl SiteLanguage {
101   pub async fn read_local(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
102     let conn = &mut get_conn(pool).await?;
103     site::table
104       .inner_join(local_site::table)
105       .inner_join(site_language::table)
106       .order(site_language::id)
107       .select(site_language::language_id)
108       .load(conn)
109       .await
110   }
111
112   pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
113     let conn = &mut get_conn(pool).await?;
114
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)
119       .load(conn)
120       .await?;
121     convert_read_languages(conn, langs).await
122   }
123
124   pub async fn update(
125     pool: &DbPool,
126     language_ids: Vec<LanguageId>,
127     site: &Site,
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;
132
133     conn
134       .build_transaction()
135       .run(|conn| {
136         Box::pin(async move {
137           use crate::schema::site_language::dsl::{site_id, site_language};
138
139           // Clear the current languages
140           delete(site_language.filter(site_id.eq(for_site_id)))
141             .execute(conn)
142             .await?;
143
144           let lang_ids = convert_update_languages(conn, language_ids).await?;
145           for l in lang_ids {
146             let form = SiteLanguageForm {
147               site_id: for_site_id,
148               language_id: l,
149             };
150             insert_into(site_language)
151               .values(form)
152               .get_result::<Self>(conn)
153               .await?;
154           }
155
156           CommunityLanguage::limit_languages(conn, instance_id).await?;
157
158           Ok(())
159         }) as _
160       })
161       .await
162   }
163 }
164
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(
168     pool: &DbPool,
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?;
174
175     if let Some(for_language_id) = for_language_id {
176       let is_allowed = select(exists(
177         community_language
178           .filter(language_id.eq(for_language_id))
179           .filter(community_id.eq(for_community_id)),
180       ))
181       .get_result(conn)
182       .await?;
183
184       if is_allowed {
185         Ok(())
186       } else {
187         Err(LemmyError::from_message("language_not_allowed"))
188       }
189     } else {
190       Ok(())
191     }
192   }
193
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> {
202     use crate::schema::{
203       community::dsl as c,
204       community_language::dsl as cl,
205       site_language::dsl as sl,
206     };
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)
213       .get_results(conn)
214       .await?;
215
216     for c in community_languages {
217       delete(cl::community_language.filter(cl::language_id.eq(c)))
218         .execute(conn)
219         .await?;
220     }
221     Ok(())
222   }
223
224   pub async fn read(
225     pool: &DbPool,
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?;
230
231     let langs = community_language
232       .filter(community_id.eq(for_community_id))
233       .order(language_id)
234       .select(language_id)
235       .get_results(conn)
236       .await?;
237     convert_read_languages(conn, langs).await
238   }
239
240   pub async fn update(
241     pool: &DbPool,
242     mut language_ids: Vec<LanguageId>,
243     for_community_id: CommunityId,
244   ) -> Result<(), Error> {
245     let conn = &mut get_conn(pool).await?;
246
247     if language_ids.is_empty() {
248       language_ids = SiteLanguage::read_local(pool).await?;
249     }
250
251     conn
252       .build_transaction()
253       .run(|conn| {
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)))
258             .execute(conn)
259             .await?;
260
261           for l in language_ids {
262             let form = CommunityLanguageForm {
263               community_id: for_community_id,
264               language_id: l,
265             };
266             insert_into(community_language)
267               .values(form)
268               .get_result::<Self>(conn)
269               .await?;
270           }
271           Ok(())
272         }) as _
273       })
274       .await
275   }
276 }
277
278 pub async fn default_post_language(
279   pool: &DbPool,
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)
291     .await?;
292
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())
298   } else {
299     Ok(None)
300   }
301 }
302
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() {
309     Ok(
310       Language::read_all_conn(conn)
311         .await?
312         .into_iter()
313         .map(|l| l.id)
314         .collect(),
315     )
316   } else {
317     Ok(language_ids)
318   }
319 }
320
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
331         .select(count(id))
332         .first(conn)
333         .await
334         .expect("read number of languages");
335       count as usize
336     })
337     .await;
338
339   if &language_ids.len() == count {
340     Ok(vec![])
341   } else {
342     Ok(language_ids)
343   }
344 }
345
346 #[cfg(test)]
347 mod tests {
348   use super::*;
349   use crate::{
350     impls::actor_language::{
351       convert_read_languages,
352       convert_update_languages,
353       default_post_language,
354       get_conn,
355       CommunityLanguage,
356       DbPool,
357       Language,
358       LanguageId,
359       LocalUserLanguage,
360       QueryDsl,
361       RunQueryDsl,
362       SiteLanguage,
363     },
364     source::{
365       community::{Community, CommunityInsertForm},
366       instance::Instance,
367       local_site::{LocalSite, LocalSiteInsertForm},
368       local_user::{LocalUser, LocalUserInsertForm},
369       person::{Person, PersonInsertForm},
370       site::{Site, SiteInsertForm},
371     },
372     traits::Crud,
373     utils::build_db_pool_for_tests,
374   };
375   use serial_test::serial;
376
377   async fn test_langs1(pool: &DbPool) -> Vec<LanguageId> {
378     vec![
379       Language::read_id_from_code(pool, Some("en"))
380         .await
381         .unwrap()
382         .unwrap(),
383       Language::read_id_from_code(pool, Some("fr"))
384         .await
385         .unwrap()
386         .unwrap(),
387       Language::read_id_from_code(pool, Some("ru"))
388         .await
389         .unwrap()
390         .unwrap(),
391     ]
392   }
393   async fn test_langs2(pool: &DbPool) -> Vec<LanguageId> {
394     vec![
395       Language::read_id_from_code(pool, Some("fi"))
396         .await
397         .unwrap()
398         .unwrap(),
399       Language::read_id_from_code(pool, Some("se"))
400         .await
401         .unwrap()
402         .unwrap(),
403     ]
404   }
405
406   async fn create_test_site(pool: &DbPool) -> (Site, Instance) {
407     let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
408       .await
409       .unwrap();
410
411     let site_form = SiteInsertForm::builder()
412       .name("test site".to_string())
413       .instance_id(inserted_instance.id)
414       .build();
415     let site = Site::create(pool, &site_form).await.unwrap();
416
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();
420
421     (site, inserted_instance)
422   }
423
424   #[tokio::test]
425   #[serial]
426   async fn test_convert_update_languages() {
427     let pool = &build_db_pool_for_tests().await;
428
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());
433
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())
437       .await
438       .unwrap();
439     assert_eq!(test_langs, converted2);
440   }
441   #[tokio::test]
442   #[serial]
443   async fn test_convert_read_languages() {
444     use crate::schema::language::dsl::{id, language};
445     let pool = &build_db_pool_for_tests().await;
446
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());
452
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())
456       .await
457       .unwrap();
458     assert_eq!(test_langs, converted2);
459   }
460
461   #[tokio::test]
462   #[serial]
463   async fn test_site_languages() {
464     let pool = &build_db_pool_for_tests().await;
465
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());
470
471     let test_langs = test_langs1(pool).await;
472     SiteLanguage::update(pool, test_langs.clone(), &site)
473       .await
474       .unwrap();
475
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);
479
480     Site::delete(pool, site.id).await.unwrap();
481     Instance::delete(pool, instance.id).await.unwrap();
482     LocalSite::delete(pool).await.unwrap();
483   }
484
485   #[tokio::test]
486   #[serial]
487   async fn test_user_languages() {
488     let pool = &build_db_pool_for_tests().await;
489
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)
493       .await
494       .unwrap();
495
496     let person_form = PersonInsertForm::builder()
497       .name("my test person".to_string())
498       .public_key("pubkey".to_string())
499       .instance_id(instance.id)
500       .build();
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())
505       .build();
506
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();
509
510     // new user should be initialized with site languages
511     assert_eq!(test_langs, local_user_langs1);
512
513     // update user languages
514     let test_langs2 = test_langs2(pool).await;
515     LocalUserLanguage::update(pool, test_langs2, local_user.id)
516       .await
517       .unwrap();
518     let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
519     assert_eq!(2, local_user_langs2.len());
520
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();
526   }
527
528   #[tokio::test]
529   #[serial]
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)
535       .await
536       .unwrap();
537
538     let read_site_langs = SiteLanguage::read(pool, site.id).await.unwrap();
539     assert_eq!(test_langs, read_site_langs);
540
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);
544
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)
550       .build();
551     let community = Community::create(pool, &community_form).await.unwrap();
552     let community_langs1 = CommunityLanguage::read(pool, community.id).await.unwrap();
553
554     // community is initialized with site languages
555     assert_eq!(test_langs, community_langs1);
556
557     let allowed_lang1 =
558       CommunityLanguage::is_allowed_community_language(pool, Some(test_langs[0]), community.id)
559         .await;
560     assert!(allowed_lang1.is_ok());
561
562     let test_langs2 = test_langs2(pool).await;
563     let allowed_lang2 =
564       CommunityLanguage::is_allowed_community_language(pool, Some(test_langs2[0]), community.id)
565         .await;
566     assert!(allowed_lang2.is_err());
567
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)
571       .await
572       .unwrap();
573     let community_langs2 = CommunityLanguage::read(pool, community.id).await.unwrap();
574     assert_eq!(vec![test_langs[0]], community_langs2);
575
576     // update community languages to different ones
577     CommunityLanguage::update(pool, test_langs2.clone(), community.id)
578       .await
579       .unwrap();
580     let community_langs3 = CommunityLanguage::read(pool, community.id).await.unwrap();
581     assert_eq!(test_langs2, community_langs3);
582
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();
587   }
588
589   #[tokio::test]
590   #[serial]
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;
597
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)
603       .build();
604     let community = Community::create(pool, &community_form).await.unwrap();
605     CommunityLanguage::update(pool, test_langs, community.id)
606       .await
607       .unwrap();
608
609     let person_form = PersonInsertForm::builder()
610       .name("my test person".to_string())
611       .public_key("pubkey".to_string())
612       .instance_id(instance.id)
613       .build();
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())
618       .build();
619     let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
620     LocalUserLanguage::update(pool, test_langs2, local_user.id)
621       .await
622       .unwrap();
623
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)
626       .await
627       .unwrap();
628     assert_eq!(None, def1);
629
630     let ru = Language::read_id_from_code(pool, Some("ru"))
631       .await
632       .unwrap()
633       .unwrap();
634     let test_langs3 = vec![
635       ru,
636       Language::read_id_from_code(pool, Some("fi"))
637         .await
638         .unwrap()
639         .unwrap(),
640       Language::read_id_from_code(pool, Some("se"))
641         .await
642         .unwrap()
643         .unwrap(),
644       UNDETERMINED_ID,
645     ];
646     LocalUserLanguage::update(pool, test_langs3, local_user.id)
647       .await
648       .unwrap();
649
650     // this time, both have ru as common lang
651     let def2 = default_post_language(pool, community.id, local_user.id)
652       .await
653       .unwrap();
654     assert_eq!(Some(ru), def2);
655
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();
662   }
663 }