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