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