3 newtypes::{CommunityId, InstanceId, LanguageId, LocalUserId, SiteId},
4 source::{actor_language::*, language::Language, site::Site},
17 use lemmy_utils::error::LemmyError;
18 use once_cell::sync::OnceCell;
20 impl LocalUserLanguage {
22 conn: &mut PgConnection,
23 for_local_user_id: LocalUserId,
24 ) -> Result<Vec<LanguageId>, Error> {
25 use crate::schema::local_user_language::dsl::*;
27 let langs = local_user_language
28 .filter(local_user_id.eq(for_local_user_id))
31 convert_read_languages(conn, langs)
34 /// Update the user's languages.
36 /// If no language_id vector is given, it will show all languages
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)?;
47 let lang_ids = convert_update_languages(conn, language_ids)?;
49 let form = LocalUserLanguageForm {
50 local_user_id: for_local_user_id,
53 insert_into(local_user_language)
55 .get_result::<Self>(conn)?;
63 pub fn read_local(conn: &mut PgConnection) -> Result<Vec<LanguageId>, Error> {
64 use crate::schema::{local_site, site, site_language};
66 .inner_join(local_site::table)
67 .inner_join(site_language::table)
68 .select(site_language::language_id)
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))
78 convert_read_languages(conn, langs)
82 conn: &mut PgConnection,
83 language_ids: Vec<LanguageId>,
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)?;
91 let lang_ids = convert_update_languages(conn, language_ids)?;
93 let form = SiteLanguageForm {
97 insert_into(site_language)
99 .get_result::<Self>(conn)?;
102 CommunityLanguage::limit_languages(conn, site.instance_id)?;
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(
120 .filter(language_id.eq(for_language_id))
121 .filter(community_id.eq(for_community_id)),
128 Err(LemmyError::from_message("language_not_allowed"))
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> {
142 community_language::dsl as cl,
143 site_language::dsl as sl,
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)
153 for c in community_languages {
154 delete(cl::community_language.filter(cl::language_id.eq(c))).execute(conn)?;
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))
168 convert_read_languages(conn, langs)
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)?;
181 if language_ids.is_empty() {
182 language_ids = SiteLanguage::read_local(conn)?;
184 for l in language_ids {
185 let form = CommunityLanguageForm {
186 community_id: for_community_id,
189 insert_into(community_language)
191 .get_result::<Self>(conn)?;
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)?;
211 if intersection.len() == 1 {
212 Ok(Some(intersection[0]))
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() {
225 Language::read_all(conn)?
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
246 .expect("read number of languages");
250 if &language_ids.len() == count {
260 impls::actor_language::*,
262 community::{Community, CommunityInsertForm},
264 local_site::{LocalSite, LocalSiteInsertForm},
265 local_user::{LocalUser, LocalUserInsertForm},
266 person::{Person, PersonInsertForm},
267 site::{Site, SiteInsertForm},
270 utils::establish_unpooled_connection,
272 use serial_test::serial;
274 fn test_langs1(conn: &mut PgConnection) -> Vec<LanguageId> {
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(),
281 fn test_langs2(conn: &mut PgConnection) -> Vec<LanguageId> {
283 Language::read_id_from_code(conn, "fi").unwrap(),
284 Language::read_id_from_code(conn, "se").unwrap(),
288 fn create_test_site(conn: &mut PgConnection) -> (Site, Instance) {
289 let inserted_instance = Instance::create(conn, "my_domain.tld").unwrap();
291 let site_form = SiteInsertForm::builder()
292 .name("test site".to_string())
293 .instance_id(inserted_instance.id)
295 let site = Site::create(conn, &site_form).unwrap();
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();
301 (site, inserted_instance)
306 fn test_convert_update_languages() {
307 let conn = &mut establish_unpooled_connection();
309 // call with empty vec, returns all languages
310 let converted1 = convert_update_languages(conn, vec![]).unwrap();
311 assert_eq!(184, converted1.len());
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);
320 fn test_convert_read_languages() {
321 let conn = &mut establish_unpooled_connection();
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());
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);
337 fn test_site_languages() {
338 let conn = &mut establish_unpooled_connection();
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());
345 let test_langs = test_langs1(conn);
346 SiteLanguage::update(conn, test_langs.clone(), &site).unwrap();
348 let site_languages2 = SiteLanguage::read_local(conn).unwrap();
349 // after update, site only has new languages
350 assert_eq!(test_langs, site_languages2);
352 Site::delete(conn, site.id).unwrap();
353 Instance::delete(conn, instance.id).unwrap();
354 LocalSite::delete(conn).unwrap();
359 fn test_user_languages() {
360 let conn = &mut establish_unpooled_connection();
362 let (site, instance) = create_test_site(conn);
363 let test_langs = test_langs1(conn);
364 SiteLanguage::update(conn, test_langs.clone(), &site).unwrap();
366 let person_form = PersonInsertForm::builder()
367 .name("my test person".to_string())
368 .public_key("pubkey".to_string())
369 .instance_id(instance.id)
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())
377 let local_user = LocalUser::create(conn, &local_user_form).unwrap();
378 let local_user_langs1 = LocalUserLanguage::read(conn, local_user.id).unwrap();
380 // new user should be initialized with site languages
381 assert_eq!(test_langs, local_user_langs1);
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());
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();
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();
404 let read_site_langs = SiteLanguage::read(conn, site.id).unwrap();
405 assert_eq!(test_langs, read_site_langs);
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);
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)
417 let community = Community::create(conn, &community_form).unwrap();
418 let community_langs1 = CommunityLanguage::read(conn, community.id).unwrap();
420 // community is initialized with site languages
421 assert_eq!(test_langs, community_langs1);
424 CommunityLanguage::is_allowed_community_language(conn, Some(test_langs[0]), community.id);
425 assert!(allowed_lang1.is_ok());
427 let test_langs2 = test_langs2(conn);
429 CommunityLanguage::is_allowed_community_language(conn, Some(test_langs2[0]), community.id);
430 assert!(allowed_lang2.is_err());
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);
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);
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();
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);
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)
463 let community = Community::create(conn, &community_form).unwrap();
464 CommunityLanguage::update(conn, test_langs, community.id).unwrap();
466 let person_form = PersonInsertForm::builder()
467 .name("my test person".to_string())
468 .public_key("pubkey".to_string())
469 .instance_id(instance.id)
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())
476 let local_user = LocalUser::create(conn, &local_user_form).unwrap();
477 LocalUserLanguage::update(conn, test_langs2, local_user.id).unwrap();
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);
483 let ru = Language::read_id_from_code(conn, "ru").unwrap();
484 let test_langs3 = vec![
486 Language::read_id_from_code(conn, "fi").unwrap(),
487 Language::read_id_from_code(conn, "se").unwrap(),
489 LocalUserLanguage::update(conn, test_langs3, local_user.id).unwrap();
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);
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();