lemmy/crates/db_schema/src/impls/actor_language.rs

630 lines
19 KiB
Rust
Raw Normal View History

use crate::{
diesel::JoinOnDsl,
newtypes::{CommunityId, InstanceId, LanguageId, LocalUserId, SiteId},
2022-11-09 10:05:00 +00:00
schema::{local_site, site, site_language},
source::{
actor_language::{
CommunityLanguage,
CommunityLanguageForm,
LocalUserLanguage,
LocalUserLanguageForm,
SiteLanguage,
SiteLanguageForm,
},
language::Language,
site::Site,
},
2022-11-09 10:05:00 +00:00
utils::{get_conn, DbPool},
};
use diesel::{
delete,
dsl::{count, exists},
insert_into,
result::Error,
select,
ExpressionMethods,
QueryDsl,
};
2022-11-09 10:05:00 +00:00
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use lemmy_utils::error::LemmyError;
2022-11-09 10:05:00 +00:00
use tokio::sync::OnceCell;
impl LocalUserLanguage {
2022-11-09 10:05:00 +00:00
pub async fn read(
pool: &DbPool,
for_local_user_id: LocalUserId,
) -> Result<Vec<LanguageId>, Error> {
use crate::schema::local_user_language::dsl::{
language_id,
local_user_id,
local_user_language,
};
2022-11-09 10:05:00 +00:00
let conn = &mut get_conn(pool).await?;
conn
.build_transaction()
.run(|conn| {
Box::pin(async move {
let langs = local_user_language
.filter(local_user_id.eq(for_local_user_id))
.order(language_id)
2022-11-09 10:05:00 +00:00
.select(language_id)
.get_results(conn)
.await?;
convert_read_languages(conn, langs).await
}) as _
})
.await
}
/// Update the user's languages.
///
/// If no language_id vector is given, it will show all languages
2022-11-09 10:05:00 +00:00
pub async fn update(
pool: &DbPool,
language_ids: Vec<LanguageId>,
for_local_user_id: LocalUserId,
) -> Result<(), Error> {
2022-11-09 10:05:00 +00:00
let conn = &mut get_conn(pool).await?;
conn
.build_transaction()
.run(|conn| {
Box::pin(async move {
use crate::schema::local_user_language::dsl::{local_user_id, local_user_language};
2022-11-09 10:05:00 +00:00
// Clear the current user languages
delete(local_user_language.filter(local_user_id.eq(for_local_user_id)))
.execute(conn)
.await?;
let lang_ids = convert_update_languages(conn, language_ids).await?;
for l in lang_ids {
let form = LocalUserLanguageForm {
local_user_id: for_local_user_id,
language_id: l,
};
insert_into(local_user_language)
.values(form)
.get_result::<Self>(conn)
.await?;
}
Ok(())
}) as _
})
.await
}
}
impl SiteLanguage {
2022-11-09 10:05:00 +00:00
pub async fn read_local(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
let conn = &mut get_conn(pool).await?;
site::table
.inner_join(local_site::table)
.inner_join(site_language::table)
.order(site_language::id)
.select(site_language::language_id)
.load(conn)
2022-11-09 10:05:00 +00:00
.await
}
2022-11-09 10:05:00 +00:00
pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
let conn = &mut get_conn(pool).await?;
let langs = site_language::table
.filter(site_language::site_id.eq(for_site_id))
.order(site_language::language_id)
2022-11-09 10:05:00 +00:00
.select(site_language::language_id)
.load(conn)
.await?;
convert_read_languages(conn, langs).await
}
2022-11-09 10:05:00 +00:00
pub async fn update(
pool: &DbPool,
language_ids: Vec<LanguageId>,
site: &Site,
) -> Result<(), Error> {
2022-11-09 10:05:00 +00:00
let conn = &mut get_conn(pool).await?;
let for_site_id = site.id;
let instance_id = site.instance_id;
conn
.build_transaction()
.run(|conn| {
Box::pin(async move {
use crate::schema::site_language::dsl::{site_id, site_language};
2022-11-09 10:05:00 +00:00
// Clear the current languages
delete(site_language.filter(site_id.eq(for_site_id)))
.execute(conn)
.await?;
let lang_ids = convert_update_languages(conn, language_ids).await?;
for l in lang_ids {
let form = SiteLanguageForm {
site_id: for_site_id,
language_id: l,
};
insert_into(site_language)
.values(form)
.get_result::<Self>(conn)
.await?;
}
CommunityLanguage::limit_languages(conn, instance_id).await?;
Ok(())
}) as _
})
.await
}
}
impl CommunityLanguage {
/// Returns true if the given language is one of configured languages for given community
2022-11-09 10:05:00 +00:00
pub async fn is_allowed_community_language(
pool: &DbPool,
for_language_id: Option<LanguageId>,
for_community_id: CommunityId,
) -> Result<(), LemmyError> {
use crate::schema::community_language::dsl::{community_id, community_language, language_id};
2022-11-09 10:05:00 +00:00
let conn = &mut get_conn(pool).await?;
if let Some(for_language_id) = for_language_id {
let is_allowed = select(exists(
community_language
.filter(language_id.eq(for_language_id))
.filter(community_id.eq(for_community_id)),
))
2022-11-09 10:05:00 +00:00
.get_result(conn)
.await?;
if is_allowed {
Ok(())
} else {
Err(LemmyError::from_message("language_not_allowed"))
}
} else {
Ok(())
}
}
/// When site languages are updated, delete all languages of local communities which are not
/// also part of site languages. This is because post/comment language is only checked against
/// community language, and it shouldnt be possible to post content in languages which are not
/// allowed by local site.
2022-11-09 10:05:00 +00:00
async fn limit_languages(
conn: &mut AsyncPgConnection,
for_instance_id: InstanceId,
) -> Result<(), Error> {
use crate::schema::{
community::dsl as c,
community_language::dsl as cl,
site_language::dsl as sl,
};
let community_languages: Vec<LanguageId> = cl::community_language
.left_outer_join(sl::site_language.on(cl::language_id.eq(sl::language_id)))
.inner_join(c::community)
.filter(c::instance_id.eq(for_instance_id))
.filter(sl::language_id.is_null())
.select(cl::language_id)
2022-11-09 10:05:00 +00:00
.get_results(conn)
.await?;
for c in community_languages {
2022-11-09 10:05:00 +00:00
delete(cl::community_language.filter(cl::language_id.eq(c)))
.execute(conn)
.await?;
}
Ok(())
}
2022-11-09 10:05:00 +00:00
pub async fn read(
pool: &DbPool,
for_community_id: CommunityId,
) -> Result<Vec<LanguageId>, Error> {
use crate::schema::community_language::dsl::{community_id, community_language, language_id};
2022-11-09 10:05:00 +00:00
let conn = &mut get_conn(pool).await?;
let langs = community_language
.filter(community_id.eq(for_community_id))
.order(language_id)
.select(language_id)
2022-11-09 10:05:00 +00:00
.get_results(conn)
.await?;
convert_read_languages(conn, langs).await
}
2022-11-09 10:05:00 +00:00
pub async fn update(
pool: &DbPool,
mut language_ids: Vec<LanguageId>,
for_community_id: CommunityId,
) -> Result<(), Error> {
2022-11-09 10:05:00 +00:00
let conn = &mut get_conn(pool).await?;
2022-11-09 10:05:00 +00:00
if language_ids.is_empty() {
language_ids = SiteLanguage::read_local(pool).await?;
}
conn
.build_transaction()
.run(|conn| {
Box::pin(async move {
use crate::schema::community_language::dsl::{community_id, community_language};
2022-11-09 10:05:00 +00:00
// Clear the current languages
delete(community_language.filter(community_id.eq(for_community_id)))
.execute(conn)
.await?;
for l in language_ids {
let form = CommunityLanguageForm {
community_id: for_community_id,
language_id: l,
};
insert_into(community_language)
.values(form)
.get_result::<Self>(conn)
.await?;
}
Ok(())
}) as _
})
.await
}
}
2022-11-09 10:05:00 +00:00
pub async fn default_post_language(
pool: &DbPool,
community_id: CommunityId,
local_user_id: LocalUserId,
) -> Result<Option<LanguageId>, Error> {
use crate::schema::{community_language::dsl as cl, local_user_language::dsl as ul};
let conn = &mut get_conn(pool).await?;
let intersection = ul::local_user_language
.inner_join(cl::community_language.on(ul::language_id.eq(cl::language_id)))
.filter(ul::local_user_id.eq(local_user_id))
.filter(cl::community_id.eq(community_id))
.select(cl::language_id)
2022-11-09 10:05:00 +00:00
.get_results::<LanguageId>(conn)
.await?;
if intersection.len() == 1 {
Ok(Some(intersection[0]))
} else {
Ok(None)
}
}
/// If no language is given, set all languages
2022-11-09 10:05:00 +00:00
async fn convert_update_languages(
conn: &mut AsyncPgConnection,
language_ids: Vec<LanguageId>,
) -> Result<Vec<LanguageId>, Error> {
if language_ids.is_empty() {
Ok(
2022-11-09 10:05:00 +00:00
Language::read_all_conn(conn)
.await?
.into_iter()
.map(|l| l.id)
.collect(),
)
} else {
Ok(language_ids)
}
}
/// If all languages are returned, return empty vec instead
2022-11-09 10:05:00 +00:00
async fn convert_read_languages(
conn: &mut AsyncPgConnection,
language_ids: Vec<LanguageId>,
) -> Result<Vec<LanguageId>, Error> {
2022-11-09 10:05:00 +00:00
static ALL_LANGUAGES_COUNT: OnceCell<usize> = OnceCell::const_new();
let count = ALL_LANGUAGES_COUNT
.get_or_init(|| async {
use crate::schema::language::dsl::{id, language};
2022-11-09 10:05:00 +00:00
let count: i64 = language
.select(count(id))
.first(conn)
.await
.expect("read number of languages");
count as usize
})
.await;
if &language_ids.len() == count {
Ok(vec![])
} else {
Ok(language_ids)
}
}
#[cfg(test)]
mod tests {
use crate::{
impls::actor_language::{
convert_read_languages,
convert_update_languages,
default_post_language,
get_conn,
CommunityLanguage,
DbPool,
Language,
LanguageId,
LocalUserLanguage,
QueryDsl,
RunQueryDsl,
SiteLanguage,
},
source::{
community::{Community, CommunityInsertForm},
instance::Instance,
local_site::{LocalSite, LocalSiteInsertForm},
local_user::{LocalUser, LocalUserInsertForm},
person::{Person, PersonInsertForm},
site::{Site, SiteInsertForm},
},
traits::Crud,
2022-11-09 10:05:00 +00:00
utils::build_db_pool_for_tests,
};
use serial_test::serial;
2022-11-09 10:05:00 +00:00
async fn test_langs1(pool: &DbPool) -> Vec<LanguageId> {
vec![
2022-11-09 10:05:00 +00:00
Language::read_id_from_code(pool, "en").await.unwrap(),
Language::read_id_from_code(pool, "fr").await.unwrap(),
Language::read_id_from_code(pool, "ru").await.unwrap(),
]
}
2022-11-09 10:05:00 +00:00
async fn test_langs2(pool: &DbPool) -> Vec<LanguageId> {
vec![
2022-11-09 10:05:00 +00:00
Language::read_id_from_code(pool, "fi").await.unwrap(),
Language::read_id_from_code(pool, "se").await.unwrap(),
]
}
2022-11-09 10:05:00 +00:00
async fn create_test_site(pool: &DbPool) -> (Site, Instance) {
let inserted_instance = Instance::create(pool, "my_domain.tld").await.unwrap();
let site_form = SiteInsertForm::builder()
.name("test site".to_string())
.instance_id(inserted_instance.id)
.build();
2022-11-09 10:05:00 +00:00
let site = Site::create(pool, &site_form).await.unwrap();
// Create a local site, since this is necessary for local languages
let local_site_form = LocalSiteInsertForm::builder().site_id(site.id).build();
2022-11-09 10:05:00 +00:00
LocalSite::create(pool, &local_site_form).await.unwrap();
(site, inserted_instance)
}
2022-11-09 10:05:00 +00:00
#[tokio::test]
#[serial]
2022-11-09 10:05:00 +00:00
async fn test_convert_update_languages() {
let pool = &build_db_pool_for_tests().await;
// call with empty vec, returns all languages
2022-11-09 10:05:00 +00:00
let conn = &mut get_conn(pool).await.unwrap();
let converted1 = convert_update_languages(conn, vec![]).await.unwrap();
assert_eq!(184, converted1.len());
// call with nonempty vec, returns same vec
2022-11-09 10:05:00 +00:00
let test_langs = test_langs1(pool).await;
let converted2 = convert_update_languages(conn, test_langs.clone())
.await
.unwrap();
assert_eq!(test_langs, converted2);
}
2022-11-09 10:05:00 +00:00
#[tokio::test]
#[serial]
2022-11-09 10:05:00 +00:00
async fn test_convert_read_languages() {
use crate::schema::language::dsl::{id, language};
2022-11-09 10:05:00 +00:00
let pool = &build_db_pool_for_tests().await;
// call with all languages, returns empty vec
2022-11-09 10:05:00 +00:00
let conn = &mut get_conn(pool).await.unwrap();
let all_langs = language.select(id).get_results(conn).await.unwrap();
let converted1: Vec<LanguageId> = convert_read_languages(conn, all_langs).await.unwrap();
assert_eq!(0, converted1.len());
// call with nonempty vec, returns same vec
2022-11-09 10:05:00 +00:00
let test_langs = test_langs1(pool).await;
let converted2 = convert_read_languages(conn, test_langs.clone())
.await
.unwrap();
assert_eq!(test_langs, converted2);
}
2022-11-09 10:05:00 +00:00
#[tokio::test]
#[serial]
2022-11-09 10:05:00 +00:00
async fn test_site_languages() {
let pool = &build_db_pool_for_tests().await;
2022-11-09 10:05:00 +00:00
let (site, instance) = create_test_site(pool).await;
let site_languages1 = SiteLanguage::read_local(pool).await.unwrap();
// site is created with all languages
assert_eq!(184, site_languages1.len());
2022-11-09 10:05:00 +00:00
let test_langs = test_langs1(pool).await;
SiteLanguage::update(pool, test_langs.clone(), &site)
.await
.unwrap();
2022-11-09 10:05:00 +00:00
let site_languages2 = SiteLanguage::read_local(pool).await.unwrap();
// after update, site only has new languages
assert_eq!(test_langs, site_languages2);
2022-11-09 10:05:00 +00:00
Site::delete(pool, site.id).await.unwrap();
Instance::delete(pool, instance.id).await.unwrap();
LocalSite::delete(pool).await.unwrap();
}
2022-11-09 10:05:00 +00:00
#[tokio::test]
#[serial]
2022-11-09 10:05:00 +00:00
async fn test_user_languages() {
let pool = &build_db_pool_for_tests().await;
2022-11-09 10:05:00 +00:00
let (site, instance) = create_test_site(pool).await;
let test_langs = test_langs1(pool).await;
SiteLanguage::update(pool, test_langs.clone(), &site)
.await
.unwrap();
let person_form = PersonInsertForm::builder()
.name("my test person".to_string())
.public_key("pubkey".to_string())
.instance_id(instance.id)
.build();
2022-11-09 10:05:00 +00:00
let person = Person::create(pool, &person_form).await.unwrap();
let local_user_form = LocalUserInsertForm::builder()
.person_id(person.id)
.password_encrypted("my_pw".to_string())
.build();
2022-11-09 10:05:00 +00:00
let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
// new user should be initialized with site languages
assert_eq!(test_langs, local_user_langs1);
// update user languages
2022-11-09 10:05:00 +00:00
let test_langs2 = test_langs2(pool).await;
LocalUserLanguage::update(pool, test_langs2, local_user.id)
.await
.unwrap();
let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
assert_eq!(2, local_user_langs2.len());
2022-11-09 10:05:00 +00:00
Person::delete(pool, person.id).await.unwrap();
LocalUser::delete(pool, local_user.id).await.unwrap();
Site::delete(pool, site.id).await.unwrap();
LocalSite::delete(pool).await.unwrap();
Instance::delete(pool, instance.id).await.unwrap();
}
2022-11-09 10:05:00 +00:00
#[tokio::test]
#[serial]
2022-11-09 10:05:00 +00:00
async fn test_community_languages() {
let pool = &build_db_pool_for_tests().await;
let (site, instance) = create_test_site(pool).await;
let test_langs = test_langs1(pool).await;
SiteLanguage::update(pool, test_langs.clone(), &site)
.await
.unwrap();
let read_site_langs = SiteLanguage::read(pool, site.id).await.unwrap();
assert_eq!(test_langs, read_site_langs);
// Test the local ones are the same
2022-11-09 10:05:00 +00:00
let read_local_site_langs = SiteLanguage::read_local(pool).await.unwrap();
assert_eq!(test_langs, read_local_site_langs);
let community_form = CommunityInsertForm::builder()
.name("test community".to_string())
.title("test community".to_string())
.public_key("pubkey".to_string())
.instance_id(instance.id)
.build();
2022-11-09 10:05:00 +00:00
let community = Community::create(pool, &community_form).await.unwrap();
let community_langs1 = CommunityLanguage::read(pool, community.id).await.unwrap();
// community is initialized with site languages
assert_eq!(test_langs, community_langs1);
let allowed_lang1 =
2022-11-09 10:05:00 +00:00
CommunityLanguage::is_allowed_community_language(pool, Some(test_langs[0]), community.id)
.await;
assert!(allowed_lang1.is_ok());
2022-11-09 10:05:00 +00:00
let test_langs2 = test_langs2(pool).await;
let allowed_lang2 =
2022-11-09 10:05:00 +00:00
CommunityLanguage::is_allowed_community_language(pool, Some(test_langs2[0]), community.id)
.await;
assert!(allowed_lang2.is_err());
// limit site languages to en, fi. after this, community languages should be updated to
// intersection of old languages (en, fr, ru) and (en, fi), which is only fi.
2022-11-09 10:05:00 +00:00
SiteLanguage::update(pool, vec![test_langs[0], test_langs2[0]], &site)
.await
.unwrap();
let community_langs2 = CommunityLanguage::read(pool, community.id).await.unwrap();
assert_eq!(vec![test_langs[0]], community_langs2);
// update community languages to different ones
2022-11-09 10:05:00 +00:00
CommunityLanguage::update(pool, test_langs2.clone(), community.id)
.await
.unwrap();
let community_langs3 = CommunityLanguage::read(pool, community.id).await.unwrap();
assert_eq!(test_langs2, community_langs3);
2022-11-09 10:05:00 +00:00
Community::delete(pool, community.id).await.unwrap();
Site::delete(pool, site.id).await.unwrap();
LocalSite::delete(pool).await.unwrap();
Instance::delete(pool, instance.id).await.unwrap();
}
2022-11-09 10:05:00 +00:00
#[tokio::test]
#[serial]
2022-11-09 10:05:00 +00:00
async fn test_default_post_language() {
let pool = &build_db_pool_for_tests().await;
let (site, instance) = create_test_site(pool).await;
let test_langs = test_langs1(pool).await;
let test_langs2 = test_langs2(pool).await;
let community_form = CommunityInsertForm::builder()
.name("test community".to_string())
.title("test community".to_string())
.public_key("pubkey".to_string())
.instance_id(instance.id)
.build();
2022-11-09 10:05:00 +00:00
let community = Community::create(pool, &community_form).await.unwrap();
CommunityLanguage::update(pool, test_langs, community.id)
.await
.unwrap();
let person_form = PersonInsertForm::builder()
.name("my test person".to_string())
.public_key("pubkey".to_string())
.instance_id(instance.id)
.build();
2022-11-09 10:05:00 +00:00
let person = Person::create(pool, &person_form).await.unwrap();
let local_user_form = LocalUserInsertForm::builder()
.person_id(person.id)
.password_encrypted("my_pw".to_string())
.build();
2022-11-09 10:05:00 +00:00
let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
LocalUserLanguage::update(pool, test_langs2, local_user.id)
.await
.unwrap();
// no overlap in user/community languages, so no default language for post
2022-11-09 10:05:00 +00:00
let def1 = default_post_language(pool, community.id, local_user.id)
.await
.unwrap();
assert_eq!(None, def1);
2022-11-09 10:05:00 +00:00
let ru = Language::read_id_from_code(pool, "ru").await.unwrap();
let test_langs3 = vec![
ru,
2022-11-09 10:05:00 +00:00
Language::read_id_from_code(pool, "fi").await.unwrap(),
Language::read_id_from_code(pool, "se").await.unwrap(),
];
2022-11-09 10:05:00 +00:00
LocalUserLanguage::update(pool, test_langs3, local_user.id)
.await
.unwrap();
// this time, both have ru as common lang
2022-11-09 10:05:00 +00:00
let def2 = default_post_language(pool, community.id, local_user.id)
.await
.unwrap();
assert_eq!(Some(ru), def2);
2022-11-09 10:05:00 +00:00
Person::delete(pool, person.id).await.unwrap();
Community::delete(pool, community.id).await.unwrap();
LocalUser::delete(pool, local_user.id).await.unwrap();
Site::delete(pool, site.id).await.unwrap();
LocalSite::delete(pool).await.unwrap();
Instance::delete(pool, instance.id).await.unwrap();
}
}