Merge pull request #2367 from LemmyNet/language-tagging-dess

Fixing up some table and join issues.
This commit is contained in:
Nutomic 2022-07-28 22:57:14 +02:00 committed by GitHub
commit faea20c87b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 228 additions and 84 deletions

View file

@ -5,10 +5,9 @@ use lemmy_api_common::{
utils::{blocking, get_local_user_view_from_jwt, send_verification_email},
};
use lemmy_db_schema::{
newtypes::LanguageId,
source::{
language::Language,
local_user::{LocalUser, LocalUserForm},
local_user_language::{LocalUserLanguage, LocalUserLanguageForm},
person::{Person, PersonForm},
site::Site,
},
@ -119,21 +118,26 @@ impl Perform for SaveUserSettings {
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "user_already_exists"))?;
let discussion_languages: Option<Vec<LanguageId>> =
if let Some(discussion_languages) = data.discussion_languages.clone() {
let mut language_ids = vec![];
for l in discussion_languages {
language_ids.push(
blocking(context.pool(), move |conn| {
Language::read_id_from_code(conn, l)
})
.await??,
);
}
Some(language_ids)
} else {
None
};
if let Some(discussion_languages) = data.discussion_languages.clone() {
// Clear the currents
blocking(context.pool(), move |conn| {
LocalUserLanguage::clear_all_for_local_user(conn, local_user_id)
})
.await??;
for language_id in discussion_languages {
let form = LocalUserLanguageForm {
local_user_id,
language_id,
};
blocking(context.pool(), move |conn| {
LocalUserLanguage::create(conn, &form)
})
.await??;
}
}
let local_user_form = LocalUserForm {
person_id: Some(person_id),

View file

@ -1,6 +1,6 @@
use crate::sensitive::Sensitive;
use lemmy_db_schema::{
newtypes::{CommunityId, DbUrl, PostId, PostReportId},
newtypes::{CommunityId, DbUrl, LanguageId, PostId, PostReportId},
ListingType,
SortType,
};
@ -17,7 +17,7 @@ pub struct CreatePost {
pub body: Option<String>,
pub honeypot: Option<String>,
pub nsfw: Option<bool>,
pub language: Option<String>,
pub language_id: Option<LanguageId>,
pub auth: Sensitive<String>,
}
@ -72,7 +72,7 @@ pub struct EditPost {
pub url: Option<Url>,
pub body: Option<String>,
pub nsfw: Option<bool>,
pub language: Option<String>,
pub language_id: Option<LanguageId>,
pub auth: Sensitive<String>,
}

View file

@ -92,25 +92,15 @@ impl PerformCrud for CreatePost {
let (embed_title, embed_description, embed_video_url) = metadata_res
.map(|u| (u.title, u.description, u.embed_video_url))
.unwrap_or_default();
let language = data.language.clone();
let mut language = blocking(context.pool(), move |conn| {
Language::read_id_from_code_opt(conn, language)
})
.await??;
// if user only speaks one language, use that as post language. otherwise, set it as "undetermined"
if language.is_none() {
let user_langs = local_user_view.local_user.discussion_languages;
language = if user_langs.len() == 1 {
Some(user_langs[0])
} else {
Some(
blocking(context.pool(), move |conn| {
Language::read_undetermined(conn)
})
.await??,
)
};
};
let language_id = Some(
data.language_id.unwrap_or(
blocking(context.pool(), move |conn| {
Language::read_undetermined(conn)
})
.await??,
),
);
let post_form = PostForm {
name: data.name.trim().to_owned(),
@ -123,7 +113,7 @@ impl PerformCrud for CreatePost {
embed_description,
embed_video_url,
thumbnail_url,
language_id: language,
language_id,
..PostForm::default()
};

View file

@ -77,11 +77,15 @@ impl PerformCrud for EditPost {
let (embed_title, embed_description, embed_video_url) = metadata_res
.map(|u| (u.title, u.description, u.embed_video_url))
.unwrap_or_default();
let language = data.language.clone();
let language = blocking(context.pool(), move |conn| {
Language::read_id_from_code_opt(conn, language)
})
.await??;
let language_id = Some(
data.language_id.unwrap_or(
blocking(context.pool(), move |conn| {
Language::read_undetermined(conn)
})
.await??,
),
);
let post_form = PostForm {
creator_id: orig_post.creator_id.to_owned(),
@ -95,7 +99,7 @@ impl PerformCrud for EditPost {
embed_description,
embed_video_url,
thumbnail_url,
language_id: language,
language_id,
..PostForm::default()
};

View file

@ -6,7 +6,7 @@ use lemmy_api_common::{
utils::{blocking, build_federated_instances, get_local_user_settings_view_from_jwt_opt},
};
use lemmy_db_schema::source::language::Language;
use lemmy_db_views::structs::SiteView;
use lemmy_db_views::structs::{LocalUserDiscussionLanguageView, SiteView};
use lemmy_db_views_actor::structs::{
CommunityBlockView,
CommunityFollowerView,
@ -84,6 +84,8 @@ impl PerformCrud for GetSite {
.await?
{
let person_id = local_user_view.person.id;
let local_user_id = local_user_view.local_user.id;
let follows = blocking(context.pool(), move |conn| {
CommunityFollowerView::for_person(conn, person_id)
})
@ -110,12 +112,19 @@ impl PerformCrud for GetSite {
.await?
.map_err(|e| LemmyError::from_error_message(e, "system_err_login"))?;
let discussion_languages = blocking(context.pool(), move |conn| {
LocalUserDiscussionLanguageView::read_languages(conn, local_user_id)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "system_err_login"))?;
Some(MyUserInfo {
local_user_view,
follows,
moderates,
community_blocks,
person_blocks,
discussion_languages,
})
} else {
None

View file

@ -187,7 +187,7 @@ impl ApubObject for ApubPost {
.map(|s| remove_slurs(&s, &context.settings().slur_regex()));
let language = page.language.map(|l| l.identifier);
let language = blocking(context.pool(), move |conn| {
Language::read_id_from_code_opt(conn, language)
Language::read_id_from_code_opt(conn, language.as_deref())
})
.await??;

View file

@ -12,14 +12,14 @@ impl Language {
language.filter(id.eq(id_)).first::<Self>(conn)
}
pub fn read_id_from_code(conn: &PgConnection, code_: String) -> Result<LanguageId, Error> {
pub fn read_id_from_code(conn: &PgConnection, code_: &str) -> Result<LanguageId, Error> {
use crate::schema::language::dsl::*;
Ok(language.filter(code.eq(code_)).first::<Self>(conn)?.id)
}
pub fn read_id_from_code_opt(
conn: &PgConnection,
code_: Option<String>,
code_: Option<&str>,
) -> Result<Option<LanguageId>, Error> {
if let Some(code_) = code_ {
Ok(Some(Language::read_id_from_code(conn, code_)?))

View file

@ -0,0 +1,51 @@
use crate::{
newtypes::{LocalUserId, LocalUserLanguageId},
source::local_user_language::*,
traits::Crud,
};
use diesel::{result::Error, PgConnection, RunQueryDsl, *};
impl Crud for LocalUserLanguage {
type Form = LocalUserLanguageForm;
type IdType = LocalUserLanguageId;
fn read(conn: &PgConnection, local_user_language_id: LocalUserLanguageId) -> Result<Self, Error> {
use crate::schema::local_user_language::dsl::*;
local_user_language
.find(local_user_language_id)
.first::<Self>(conn)
}
fn create(
conn: &PgConnection,
local_user_language_form: &LocalUserLanguageForm,
) -> Result<Self, Error> {
use crate::schema::local_user_language::dsl::*;
insert_into(local_user_language)
.values(local_user_language_form)
.on_conflict((local_user_id, language_id))
.do_update()
.set(local_user_language_form)
.get_result::<Self>(conn)
}
fn update(
conn: &PgConnection,
local_user_language_id: LocalUserLanguageId,
local_user_language_form: &LocalUserLanguageForm,
) -> Result<Self, Error> {
use crate::schema::local_user_language::dsl::*;
diesel::update(local_user_language.find(local_user_language_id))
.set(local_user_language_form)
.get_result::<Self>(conn)
}
}
impl LocalUserLanguage {
pub fn clear_all_for_local_user(
conn: &PgConnection,
for_local_user_id: LocalUserId,
) -> Result<usize, Error> {
use crate::schema::local_user_language::dsl::*;
diesel::delete(local_user_language.filter(local_user_id.eq(for_local_user_id))).execute(conn)
}
}

View file

@ -6,6 +6,7 @@ pub mod community_block;
pub mod email_verification;
pub mod language;
pub mod local_user;
pub mod local_user_language;
pub mod moderator;
pub mod password_reset_request;
pub mod person;

View file

@ -72,6 +72,10 @@ pub struct PostReportId(i32);
#[cfg_attr(feature = "full", derive(DieselNewType))]
pub struct LanguageId(pub i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType))]
pub struct LocalUserLanguageId(pub i32);
#[repr(transparent)]
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))]

View file

@ -0,0 +1,22 @@
use crate::newtypes::{LanguageId, LocalUserId, LocalUserLanguageId};
use serde::{Deserialize, Serialize};
#[cfg(feature = "full")]
use crate::schema::local_user_language;
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "full", derive(Queryable, Identifiable))]
#[cfg_attr(feature = "full", table_name = "local_user_language")]
pub struct LocalUserLanguage {
#[serde(skip)]
pub id: LocalUserLanguageId,
pub local_user_id: LocalUserId,
pub language_id: LanguageId,
}
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", table_name = "local_user_language")]
pub struct LocalUserLanguageForm {
pub local_user_id: LocalUserId,
pub language_id: LanguageId,
}

View file

@ -7,6 +7,7 @@ pub mod community_block;
pub mod email_verification;
pub mod language;
pub mod local_user;
pub mod local_user_language;
pub mod moderator;
pub mod password_reset_request;
pub mod person;

View file

@ -6,6 +6,8 @@ pub mod comment_report_view;
#[cfg(feature = "full")]
pub mod comment_view;
#[cfg(feature = "full")]
pub mod local_user_discussion_language_view;
#[cfg(feature = "full")]
pub mod local_user_view;
#[cfg(feature = "full")]
pub mod post_report_view;

View file

@ -34,4 +34,11 @@ impl LocalUserDiscussionLanguageView {
.collect::<Vec<Self>>(),
)
}
pub fn read_languages(
conn: &PgConnection,
local_user_id: LocalUserId,
) -> Result<Vec<Language>, Error> {
Self::read(conn, local_user_id).map(|r| r.into_iter().map(|a| a.language).collect())
}
}

View file

@ -7,12 +7,13 @@ use diesel::{
};
use lemmy_db_schema::{
aggregates::structs::PostAggregates,
newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId},
newtypes::{CommunityId, DbUrl, LocalUserId, PersonId, PostId},
schema::{
community,
community_block,
community_follower,
community_person_ban,
language,
local_user_language,
person,
person_block,
@ -58,7 +59,6 @@ impl PostView {
) -> Result<Self, Error> {
// The left join below will return None in this case
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
let (
post,
creator,
@ -70,7 +70,7 @@ impl PostView {
read,
creator_blocked,
post_like,
local_user_language,
language,
) = post::table
.find(post_id)
.inner_join(person::table)
@ -123,7 +123,7 @@ impl PostView {
.and(post_like::person_id.eq(person_id_join)),
),
)
.inner_join(local_user_language::table)
.inner_join(language::table)
.select((
post::all_columns,
Person::safe_columns_tuple(),
@ -135,7 +135,7 @@ impl PostView {
post_read::all_columns.nullable(),
person_block::all_columns.nullable(),
post_like::score.nullable(),
local_user_language::all_columns,
language::all_columns,
))
.first::<PostViewTuple>(conn)?;
@ -158,6 +158,7 @@ impl PostView {
read: read.is_some(),
creator_blocked: creator_blocked.is_some(),
my_vote,
language,
})
}
}
@ -170,13 +171,13 @@ pub struct PostQueryBuilder<'a> {
community_id: Option<CommunityId>,
community_actor_id: Option<DbUrl>,
my_person_id: Option<PersonId>,
my_local_user_id: Option<LocalUserId>,
search_term: Option<String>,
url_search: Option<String>,
show_nsfw: Option<bool>,
show_bot_accounts: Option<bool>,
show_read_posts: Option<bool>,
saved_only: Option<bool>,
languages: Option<Vec<LanguageId>>,
page: Option<i64>,
limit: Option<i64>,
}
@ -191,13 +192,13 @@ impl<'a> PostQueryBuilder<'a> {
community_id: None,
community_actor_id: None,
my_person_id: None,
my_local_user_id: None,
search_term: None,
url_search: None,
show_nsfw: None,
show_bot_accounts: None,
show_read_posts: None,
saved_only: None,
languages: None,
page: None,
limit: None,
}
@ -256,10 +257,10 @@ impl<'a> PostQueryBuilder<'a> {
pub fn set_params_for_user(mut self, user: &Option<LocalUserView>) -> Self {
if let Some(user) = user {
self.my_person_id = Some(user.person.id);
self.my_local_user_id = Some(user.local_user.id);
self.show_nsfw = Some(user.local_user.show_nsfw);
self.show_bot_accounts = Some(user.local_user.show_bot_accounts);
self.show_read_posts = Some(user.local_user.show_read_posts);
//TODO: add local user discussion languages (need to store them in LocalUserView?)
}
self
}
@ -269,6 +270,7 @@ impl<'a> PostQueryBuilder<'a> {
// The left join below will return None in this case
let person_id_join = self.my_person_id.unwrap_or(PersonId(-1));
let local_user_id_join = self.my_local_user_id.unwrap_or(LocalUserId(-1));
let mut query = post::table
.inner_join(person::table)
@ -328,6 +330,14 @@ impl<'a> PostQueryBuilder<'a> {
.and(post_like::person_id.eq(person_id_join)),
),
)
.inner_join(language::table)
.left_join(
local_user_language::table.on(
post::language_id
.eq(local_user_language::language_id)
.and(local_user_language::local_user_id.eq(local_user_id_join)),
),
)
.select((
post::all_columns,
Person::safe_columns_tuple(),
@ -339,6 +349,7 @@ impl<'a> PostQueryBuilder<'a> {
post_read::all_columns.nullable(),
person_block::all_columns.nullable(),
post_like::score.nullable(),
language::all_columns,
))
.into_boxed();
@ -418,8 +429,9 @@ impl<'a> PostQueryBuilder<'a> {
query = query.filter(post_read::id.is_null());
}
if let Some(languages) = self.languages {
// TODO
// Filter out the rows with missing languages
if self.my_local_user_id.is_some() {
query = query.filter(local_user_language::id.is_not_null());
}
// Don't show blocked communities or persons
@ -501,6 +513,7 @@ impl ViewToVec for PostView {
read: a.7.is_some(),
creator_blocked: a.8.is_some(),
my_vote: a.9,
language: a.10,
})
.collect::<Vec<Self>>()
}
@ -516,6 +529,9 @@ mod tests {
source::{
community::*,
community_block::{CommunityBlock, CommunityBlockForm},
language::Language,
local_user::{LocalUser, LocalUserForm},
local_user_language::{LocalUserLanguage, LocalUserLanguageForm},
person::*,
person_block::{PersonBlock, PersonBlockForm},
post::*,
@ -530,6 +546,7 @@ mod tests {
struct Data {
inserted_person: Person,
inserted_local_user: LocalUser,
inserted_blocked_person: Person,
inserted_bot: Person,
inserted_community: Community,
@ -550,6 +567,24 @@ mod tests {
let inserted_person = Person::create(conn, &new_person).unwrap();
let new_local_user = LocalUserForm {
person_id: Some(inserted_person.id),
password_encrypted: Some("".to_string()),
..LocalUserForm::default()
};
let inserted_local_user = LocalUser::create(conn, &new_local_user).unwrap();
let french_id = Language::read_id_from_code(conn, "fr").unwrap();
// Insert french
let new_local_user_language = LocalUserLanguageForm {
local_user_id: inserted_local_user.id,
language_id: french_id,
};
let _inserted_local_user_language =
LocalUserLanguage::create(conn, &new_local_user_language).unwrap();
let new_bot = PersonForm {
name: person_name.to_owned(),
bot_account: Some(true),
@ -600,7 +635,7 @@ mod tests {
name: post_name,
creator_id: inserted_person.id,
community_id: inserted_community.id,
language_id: Some(LanguageId(2)),
language_id: Some(LanguageId(47)),
..PostForm::default()
};
@ -617,6 +652,7 @@ mod tests {
Data {
inserted_person,
inserted_local_user,
inserted_blocked_person,
inserted_bot,
inserted_community,
@ -662,7 +698,7 @@ mod tests {
thumbnail_url: None,
ap_id: inserted_post.ap_id.to_owned(),
local: true,
language_id: LanguageId(2),
language_id: LanguageId(47),
},
my_vote: None,
creator: PersonSafe {
@ -719,6 +755,11 @@ mod tests {
read: false,
saved: false,
creator_blocked: false,
language: Language {
id: LanguageId(47),
code: "fr".to_string(),
name: "Français".to_string(),
},
}
}
@ -853,18 +894,28 @@ mod tests {
let conn = establish_unpooled_connection();
let data = init_data(&conn);
let mut read_post_listing_french = PostQueryBuilder::create(&conn)
let read_post_listings_no_user = PostQueryBuilder::create(&conn)
.listing_type(ListingType::Community)
.sort(SortType::New)
.community_id(data.inserted_community.id)
.list()
.unwrap();
let fr = LanguageId(47);
assert_eq!(3, read_post_listings_no_user.len());
assert_eq!(fr, read_post_listings_no_user[1].post.language_id);
let mut read_post_listings_with_user = PostQueryBuilder::create(&conn)
.listing_type(ListingType::Community)
.sort(SortType::New)
.community_id(data.inserted_community.id);
let fr = LanguageId(2);
read_post_listing_french.languages = Some(vec![fr]);
let read_post_listing_french = read_post_listing_french.list().unwrap();
read_post_listings_with_user.my_local_user_id = Some(data.inserted_local_user.id);
let expected_post_listing = expected_post_listing(&data, &conn);
let read_post_listings_with_user = read_post_listings_with_user.list().unwrap();
assert_eq!(1, read_post_listing_french.len());
assert_eq!(expected_post_listing, read_post_listing_french[0]);
assert_eq!(1, read_post_listings_with_user.len());
assert_eq!(fr, read_post_listings_with_user[0].post.language_id);
cleanup(data, &conn);
}

View file

@ -4,6 +4,7 @@ use lemmy_db_schema::{
comment::Comment,
comment_report::CommentReport,
community::CommunitySafe,
language::Language,
local_user::{LocalUser, LocalUserSettings},
person::{Person, PersonSafe, PersonSafeAlias1, PersonSafeAlias2},
post::Post,
@ -84,6 +85,7 @@ pub struct PostView {
pub read: bool, // Left join to PostRead
pub creator_blocked: bool, // Left join to PersonBlock
pub my_vote: Option<i16>, // Left join to PostLike
pub language: Language,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@ -106,3 +108,9 @@ pub struct SiteView {
pub site: Site,
pub counts: SiteAggregates,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LocalUserDiscussionLanguageView {
pub local_user: LocalUserSettings,
pub language: Language,
}

View file

@ -9,8 +9,6 @@ pub mod community_person_ban_view;
#[cfg(feature = "full")]
pub mod community_view;
#[cfg(feature = "full")]
pub mod local_user_discusion_language_view;
#[cfg(feature = "full")]
pub mod person_block_view;
#[cfg(feature = "full")]
pub mod person_mention_view;

View file

@ -3,8 +3,6 @@ use lemmy_db_schema::{
source::{
comment::Comment,
community::CommunitySafe,
language::Language,
local_user::LocalUserSettings,
person::{PersonSafe, PersonSafeAlias1},
person_mention::PersonMention,
post::Post,
@ -72,9 +70,3 @@ pub struct PersonViewSafe {
pub person: PersonSafe,
pub counts: PersonAggregates,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LocalUserDiscussionLanguageView {
pub local_user: LocalUserSettings,
pub language: Language,
}

View file

@ -1,7 +1,6 @@
drop table language;
alter table post drop column language_id;
drop table local_user_language;
drop table language;
alter table local_user rename column interface_language to lang;
alter table local_user drop column discussion_languages;
alter table post drop column language_id;

View file

@ -6,12 +6,13 @@ create table language (
create table local_user_language (
id serial primary key,
local_user_id int references local_user on update cascade on delete cascade not null,
language_id int references language_id on update cascade on delete cascade not null,
language_id int references language on update cascade on delete cascade not null,
unique (local_user_id, language_id)
);
alter table local_user rename column lang to interface_language;
alter table post add column language_id integer not null default 0;
alter table post add column language_id integer references language not null default 0;
insert into language(id, code, name) values (0, 'und', 'Undetermined');
insert into language(code, name) values ('aa', 'Afaraf');