Implement language tagging for posts, comments and PMs (fixes #440)

feature/language-tags
Felix Ableitner 2021-04-16 17:02:23 +02:00
parent 65a11a7239
commit 1151a105bc
28 changed files with 207 additions and 25 deletions

13
Cargo.lock generated
View File

@ -122,7 +122,7 @@ dependencies = [
"httparse", "httparse",
"indexmap", "indexmap",
"itoa", "itoa",
"language-tags", "language-tags 0.2.2",
"lazy_static", "lazy_static",
"log", "log",
"mime", "mime",
@ -1568,7 +1568,7 @@ dependencies = [
"http", "http",
"httparse", "httparse",
"httpdate", "httpdate",
"language-tags", "language-tags 0.2.2",
"mime", "mime",
"percent-encoding", "percent-encoding",
"unicase", "unicase",
@ -1719,6 +1719,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
[[package]]
name = "language-tags"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3b2cac4f8e25c09c46826144a8b6e78520351824c247eff1a69a2aa6ebab26"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -1932,6 +1938,7 @@ dependencies = [
"chrono", "chrono",
"diesel", "diesel",
"diesel_migrations", "diesel_migrations",
"language-tags 0.3.0",
"lazy_static", "lazy_static",
"lemmy_db_schema", "lemmy_db_schema",
"lemmy_utils", "lemmy_utils",
@ -1953,6 +1960,7 @@ dependencies = [
"chrono", "chrono",
"diesel", "diesel",
"diesel-derive-newtype", "diesel-derive-newtype",
"language-tags 0.3.0",
"log", "log",
"serde", "serde",
"serde_json", "serde_json",
@ -1964,6 +1972,7 @@ name = "lemmy_db_views"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"diesel", "diesel",
"language-tags 0.3.0",
"lemmy_db_queries", "lemmy_db_queries",
"lemmy_db_schema", "lemmy_db_schema",
"log", "log",

View File

@ -235,9 +235,10 @@ impl Perform for SaveUserSettings {
theme: data.theme.to_owned(), theme: data.theme.to_owned(),
default_sort_type, default_sort_type,
default_listing_type, default_listing_type,
lang: data.lang.to_owned(), interface_language: data.interface_language.to_owned(),
show_avatars: data.show_avatars, show_avatars: data.show_avatars,
send_notifications_to_email: data.send_notifications_to_email, send_notifications_to_email: data.send_notifications_to_email,
discussion_languages: data.discussion_languages.to_owned(),
}; };
let local_user_res = blocking(context.pool(), move |conn| { let local_user_res = blocking(context.pool(), move |conn| {

View File

@ -9,6 +9,7 @@ pub struct CreateComment {
pub post_id: PostId, pub post_id: PostId,
pub form_id: Option<String>, pub form_id: Option<String>,
pub auth: String, pub auth: String,
pub language: String,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -17,6 +18,7 @@ pub struct EditComment {
pub comment_id: CommentId, pub comment_id: CommentId,
pub form_id: Option<String>, pub form_id: Option<String>,
pub auth: String, pub auth: String,
pub language: String,
} }
#[derive(Deserialize)] #[derive(Deserialize)]

View File

@ -16,7 +16,7 @@ pub struct Login {
pub username_or_email: String, pub username_or_email: String,
pub password: String, pub password: String,
} }
use lemmy_db_schema::{CommunityId, PersonId, PersonMentionId, PrivateMessageId}; use lemmy_db_schema::{CommunityId, DbLanguage, PersonId, PersonMentionId, PrivateMessageId};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Register { pub struct Register {
@ -27,6 +27,7 @@ pub struct Register {
pub show_nsfw: bool, pub show_nsfw: bool,
pub captcha_uuid: Option<String>, pub captcha_uuid: Option<String>,
pub captcha_answer: Option<String>, pub captcha_answer: Option<String>,
pub discussion_languages: Vec<DbLanguage>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -51,7 +52,7 @@ pub struct SaveUserSettings {
pub theme: Option<String>, pub theme: Option<String>,
pub default_sort_type: Option<i16>, pub default_sort_type: Option<i16>,
pub default_listing_type: Option<i16>, pub default_listing_type: Option<i16>,
pub lang: Option<String>, pub interface_language: Option<String>,
pub avatar: Option<String>, pub avatar: Option<String>,
pub banner: Option<String>, pub banner: Option<String>,
pub display_name: Option<String>, pub display_name: Option<String>,
@ -61,6 +62,7 @@ pub struct SaveUserSettings {
pub show_avatars: Option<bool>, pub show_avatars: Option<bool>,
pub send_notifications_to_email: Option<bool>, pub send_notifications_to_email: Option<bool>,
pub auth: String, pub auth: String,
pub discussion_languages: Option<Vec<DbLanguage>>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]

View File

@ -19,6 +19,7 @@ pub struct CreatePost {
pub nsfw: bool, pub nsfw: bool,
pub community_id: CommunityId, pub community_id: CommunityId,
pub auth: String, pub auth: String,
pub language: String,
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
@ -73,6 +74,7 @@ pub struct EditPost {
pub body: Option<String>, pub body: Option<String>,
pub nsfw: bool, pub nsfw: bool,
pub auth: String, pub auth: String,
pub language: String,
} }
#[derive(Deserialize)] #[derive(Deserialize)]

View File

@ -37,6 +37,7 @@ impl PerformCrud for GetSite {
show_nsfw: true, show_nsfw: true,
captcha_uuid: None, captcha_uuid: None,
captcha_answer: None, captcha_answer: None,
discussion_languages: vec![],
}; };
let login_response = register.perform(context, websocket_id).await?; let login_response = register.perform(context, websocket_id).await?;
info!("Admin {} created", setup.admin_username); info!("Admin {} created", setup.admin_username);

View File

@ -126,10 +126,11 @@ impl PerformCrud for Register {
theme: Some("browser".into()), theme: Some("browser".into()),
default_sort_type: Some(SortType::Active as i16), default_sort_type: Some(SortType::Active as i16),
default_listing_type: Some(ListingType::Subscribed as i16), default_listing_type: Some(ListingType::Subscribed as i16),
lang: Some("browser".into()), interface_language: Some("browser".into()),
show_avatars: Some(true), show_avatars: Some(true),
show_scores: Some(true), show_scores: Some(true),
send_notifications_to_email: Some(false), send_notifications_to_email: Some(false),
discussion_languages: Some(data.discussion_languages.to_owned()),
}; };
let inserted_local_user = match blocking(context.pool(), move |conn| { let inserted_local_user = match blocking(context.pool(), move |conn| {

View File

@ -1,5 +1,6 @@
pub mod context; pub mod context;
pub(crate) mod group_extension; pub(crate) mod group_extension;
pub(crate) mod note_extension;
pub(crate) mod page_extension; pub(crate) mod page_extension;
pub(crate) mod person_extension; pub(crate) mod person_extension;
pub mod signatures; pub mod signatures;

View File

@ -0,0 +1,40 @@
use activitystreams::unparsed::UnparsedMutExt;
use activitystreams_ext::UnparsedExtension;
use lemmy_db_schema::DbLanguage;
use lemmy_utils::LemmyError;
use serde::{Deserialize, Serialize};
/// Activitystreams extension to allow (de)serializing additional Post fields
/// `comemnts_enabled` (called 'locked' in Lemmy),
/// `sensitive` (called 'nsfw') and `stickied`.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NoteExtension {
pub language: Option<DbLanguage>,
}
impl NoteExtension {
pub fn new(language: DbLanguage) -> Result<NoteExtension, LemmyError> {
Ok(NoteExtension {
language: Some(language),
})
}
}
impl<U> UnparsedExtension<U> for NoteExtension
where
U: UnparsedMutExt,
{
type Error = serde_json::Error;
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
Ok(NoteExtension {
language: unparsed_mut.remove("language")?,
})
}
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("language", self.language)?;
Ok(())
}
}

View File

@ -1,5 +1,7 @@
use activitystreams::unparsed::UnparsedMutExt; use activitystreams::unparsed::UnparsedMutExt;
use activitystreams_ext::UnparsedExtension; use activitystreams_ext::UnparsedExtension;
use lemmy_db_schema::DbLanguage;
use lemmy_utils::LemmyError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Activitystreams extension to allow (de)serializing additional Post fields /// Activitystreams extension to allow (de)serializing additional Post fields
@ -11,6 +13,23 @@ pub struct PageExtension {
pub comments_enabled: Option<bool>, pub comments_enabled: Option<bool>,
pub sensitive: Option<bool>, pub sensitive: Option<bool>,
pub stickied: Option<bool>, pub stickied: Option<bool>,
pub language: Option<DbLanguage>,
}
impl PageExtension {
pub fn new(
comments_enabled: bool,
sensitive: bool,
stickied: bool,
language: DbLanguage,
) -> Result<PageExtension, LemmyError> {
Ok(PageExtension {
comments_enabled: Some(comments_enabled),
sensitive: Some(sensitive),
stickied: Some(stickied),
language: Some(language),
})
}
} }
impl<U> UnparsedExtension<U> for PageExtension impl<U> UnparsedExtension<U> for PageExtension
@ -24,6 +43,7 @@ where
comments_enabled: unparsed_mut.remove("commentsEnabled")?, comments_enabled: unparsed_mut.remove("commentsEnabled")?,
sensitive: unparsed_mut.remove("sensitive")?, sensitive: unparsed_mut.remove("sensitive")?,
stickied: unparsed_mut.remove("stickied")?, stickied: unparsed_mut.remove("stickied")?,
language: unparsed_mut.remove("language")?,
}) })
} }
@ -31,6 +51,7 @@ where
unparsed_mut.insert("commentsEnabled", self.comments_enabled)?; unparsed_mut.insert("commentsEnabled", self.comments_enabled)?;
unparsed_mut.insert("sensitive", self.sensitive)?; unparsed_mut.insert("sensitive", self.sensitive)?;
unparsed_mut.insert("stickied", self.stickied)?; unparsed_mut.insert("stickied", self.stickied)?;
unparsed_mut.insert("language", self.language)?;
Ok(()) Ok(())
} }
} }

View File

@ -10,6 +10,7 @@ pub mod objects;
use crate::{ use crate::{
extensions::{ extensions::{
group_extension::GroupExtension, group_extension::GroupExtension,
note_extension::NoteExtension,
page_extension::PageExtension, page_extension::PageExtension,
person_extension::PersonExtension, person_extension::PersonExtension,
signatures::{PublicKey, PublicKeyExtension}, signatures::{PublicKey, PublicKeyExtension},
@ -53,7 +54,7 @@ pub type GroupExt =
type PersonExt = Ext2<actor::ApActor<ApObject<actor::Person>>, PersonExtension, PublicKeyExtension>; type PersonExt = Ext2<actor::ApActor<ApObject<actor::Person>>, PersonExtension, PublicKeyExtension>;
/// Activitystreams type for post /// Activitystreams type for post
pub type PageExt = Ext1<ApObject<Page>, PageExtension>; pub type PageExt = Ext1<ApObject<Page>, PageExtension>;
pub type NoteExt = ApObject<Note>; pub type NoteExt = Ext1<ApObject<Note>, NoteExtension>;
pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json"; pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
extensions::context::lemmy_context, extensions::{context::lemmy_context, note_extension::NoteExtension},
fetcher::objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, fetcher::objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
objects::{ objects::{
check_object_domain, check_object_domain,
@ -20,6 +20,7 @@ use activitystreams::{
prelude::*, prelude::*,
public, public,
}; };
use activitystreams_ext::Ext1;
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_db_queries::{Crud, DbPool}; use lemmy_db_queries::{Crud, DbPool};
@ -82,7 +83,8 @@ impl ToApub for Comment {
comment.set_updated(convert_datetime(u)); comment.set_updated(convert_datetime(u));
} }
Ok(comment) let ext = NoteExtension::new(self.language.to_owned())?;
Ok(Ext1::new(comment, ext))
} }
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> { fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
@ -204,6 +206,7 @@ impl FromApubToForm<NoteExt> for CommentForm {
deleted: None, deleted: None,
ap_id: Some(check_object_domain(note, expected_domain)?), ap_id: Some(check_object_domain(note, expected_domain)?),
local: Some(false), local: Some(false),
language: note.ext_one.language.to_owned(),
}) })
} }
} }

View File

@ -88,11 +88,12 @@ impl ToApub for Post {
page.set_updated(convert_datetime(u)); page.set_updated(convert_datetime(u));
} }
let ext = PageExtension { let ext = PageExtension::new(
comments_enabled: Some(!self.locked), !self.locked,
sensitive: Some(self.nsfw), self.nsfw,
stickied: Some(self.stickied), self.stickied,
}; self.language.to_owned(),
)?;
Ok(Ext1::new(page, ext)) Ok(Ext1::new(page, ext))
} }
@ -239,6 +240,7 @@ impl FromApubToForm<PageExt> for PostForm {
thumbnail_url: pictrs_thumbnail.map(|u| u.into()), thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
ap_id: Some(ap_id), ap_id: Some(ap_id),
local: Some(false), local: Some(false),
language: ext.language.to_owned(),
}) })
} }
} }

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
check_is_apub_id_valid, check_is_apub_id_valid,
extensions::context::lemmy_context, extensions::{context::lemmy_context, note_extension::NoteExtension},
fetcher::person::get_or_fetch_and_upsert_person, fetcher::person::get_or_fetch_and_upsert_person,
objects::{ objects::{
check_object_domain, check_object_domain,
@ -18,6 +18,7 @@ use activitystreams::{
object::{kind::NoteType, ApObject, Note, Tombstone}, object::{kind::NoteType, ApObject, Note, Tombstone},
prelude::*, prelude::*,
}; };
use activitystreams_ext::Ext1;
use anyhow::Context; use anyhow::Context;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_db_queries::{Crud, DbPool}; use lemmy_db_queries::{Crud, DbPool};
@ -55,7 +56,8 @@ impl ToApub for PrivateMessage {
private_message.set_updated(convert_datetime(u)); private_message.set_updated(convert_datetime(u));
} }
Ok(private_message) let ext = NoteExtension::new(self.language.to_owned())?;
Ok(Ext1::new(private_message, ext))
} }
fn to_tombstone(&self) -> Result<Tombstone, LemmyError> { fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
@ -131,6 +133,7 @@ impl FromApubToForm<NoteExt> for PrivateMessageForm {
read: None, read: None,
ap_id: Some(check_object_domain(note, expected_domain)?), ap_id: Some(check_object_domain(note, expected_domain)?),
local: Some(false), local: Some(false),
language: note.ext_one.language.to_owned(),
}) })
} }
} }

View File

@ -24,6 +24,7 @@ url = { version = "2.2.1", features = ["serde"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
regex = "1.4.3" regex = "1.4.3"
bcrypt = "0.9.0" bcrypt = "0.9.0"
language-tags = "0.3.0"
[dev-dependencies] [dev-dependencies]
serial_test = "0.5.1" serial_test = "0.5.1"

View File

@ -290,6 +290,7 @@ mod tests {
updated: None, updated: None,
ap_id: inserted_comment.ap_id.to_owned(), ap_id: inserted_comment.ap_id.to_owned(),
local: true, local: true,
language: None,
}; };
let child_comment_form = CommentForm { let child_comment_form = CommentForm {

View File

@ -20,7 +20,7 @@ mod safe_settings_type {
theme, theme,
default_sort_type, default_sort_type,
default_listing_type, default_listing_type,
lang, interface_language,
show_avatars, show_avatars,
send_notifications_to_email, send_notifications_to_email,
validator_time, validator_time,
@ -40,7 +40,7 @@ mod safe_settings_type {
theme, theme,
default_sort_type, default_sort_type,
default_listing_type, default_listing_type,
lang, interface_language,
show_avatars, show_avatars,
send_notifications_to_email, send_notifications_to_email,
validator_time, validator_time,

View File

@ -315,6 +315,7 @@ mod tests {
thumbnail_url: None, thumbnail_url: None,
ap_id: inserted_post.ap_id.to_owned(), ap_id: inserted_post.ap_id.to_owned(),
local: true, local: true,
language: None,
}; };
// Post Like // Post Like

View File

@ -14,3 +14,4 @@ serde_json = { version = "1.0.61", features = ["preserve_order"] }
log = "0.4.14" log = "0.4.14"
url = { version = "2.2.1", features = ["serde"] } url = { version = "2.2.1", features = ["serde"] }
diesel-derive-newtype = "0.1" diesel-derive-newtype = "0.1"
language-tags = "0.3.0"

View File

@ -11,7 +11,8 @@ use diesel::{
serialize::{Output, ToSql}, serialize::{Output, ToSql},
sql_types::Text, sql_types::Text,
}; };
use serde::{Deserialize, Serialize}; use language_tags::LanguageTag;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{ use std::{
fmt, fmt,
fmt::{Display, Formatter}, fmt::{Display, Formatter},
@ -119,3 +120,71 @@ impl From<Url> for DbUrl {
pub fn naive_now() -> NaiveDateTime { pub fn naive_now() -> NaiveDateTime {
chrono::prelude::Utc::now().naive_utc() chrono::prelude::Utc::now().naive_utc()
} }
#[repr(transparent)]
#[derive(Clone, PartialEq, Debug, AsExpression, FromSqlRow)]
#[sql_type = "Text"]
pub struct DbLanguage(LanguageTag);
impl<DB: Backend> ToSql<Text, DB> for DbLanguage
where
String: ToSql<Text, DB>,
{
fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> diesel::serialize::Result {
self.0.to_string().to_sql(out)
}
}
// TODO: hopefully the crate will add serde support
// https://github.com/pyfisch/rust-language-tags/issues/22
impl Serialize for DbLanguage {
fn serialize<S>(&self, _serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
todo!()
}
}
impl<'de> Deserialize<'de> for DbLanguage {
fn deserialize<D>(_deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
todo!()
}
}
impl<DB: Backend> FromSql<Text, DB> for DbLanguage
where
String: FromSql<Text, DB>,
{
fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> {
let str = String::from_sql(bytes)?;
Ok(DbLanguage(LanguageTag::parse(&str)?))
}
}
impl DbLanguage {
pub fn into_inner(self) -> LanguageTag {
self.0
}
}
impl Display for DbLanguage {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.to_owned().into_inner().fmt(f)
}
}
impl From<DbLanguage> for LanguageTag {
fn from(url: DbLanguage) -> Self {
url.0
}
}
impl From<LanguageTag> for DbLanguage {
fn from(lang: LanguageTag) -> Self {
DbLanguage(lang)
}
}

View File

@ -24,6 +24,7 @@ table! {
deleted -> Bool, deleted -> Bool,
ap_id -> Varchar, ap_id -> Varchar,
local -> Bool, local -> Bool,
language -> Text,
} }
} }
@ -149,11 +150,12 @@ table! {
theme -> Varchar, theme -> Varchar,
default_sort_type -> Int2, default_sort_type -> Int2,
default_listing_type -> Int2, default_listing_type -> Int2,
lang -> Varchar, interface_language -> Varchar,
show_avatars -> Bool, show_avatars -> Bool,
send_notifications_to_email -> Bool, send_notifications_to_email -> Bool,
validator_time -> Timestamp, validator_time -> Timestamp,
show_scores -> Bool, show_scores -> Bool,
discussion_languages -> Array<Text>,
} }
} }
@ -340,6 +342,7 @@ table! {
thumbnail_url -> Nullable<Text>, thumbnail_url -> Nullable<Text>,
ap_id -> Varchar, ap_id -> Varchar,
local -> Bool, local -> Bool,
language -> Text,
} }
} }
@ -414,6 +417,7 @@ table! {
updated -> Nullable<Timestamp>, updated -> Nullable<Timestamp>,
ap_id -> Varchar, ap_id -> Varchar,
local -> Bool, local -> Bool,
language -> Text,
} }
} }

View File

@ -2,6 +2,7 @@ use crate::{
schema::{comment, comment_alias_1, comment_like, comment_saved}, schema::{comment, comment_alias_1, comment_like, comment_saved},
source::post::Post, source::post::Post,
CommentId, CommentId,
DbLanguage,
DbUrl, DbUrl,
PersonId, PersonId,
PostId, PostId,
@ -31,6 +32,7 @@ pub struct Comment {
pub deleted: bool, pub deleted: bool,
pub ap_id: DbUrl, pub ap_id: DbUrl,
pub local: bool, pub local: bool,
pub language: DbLanguage,
} }
#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)] #[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
@ -65,6 +67,7 @@ pub struct CommentForm {
pub deleted: Option<bool>, pub deleted: Option<bool>,
pub ap_id: Option<DbUrl>, pub ap_id: Option<DbUrl>,
pub local: Option<bool>, pub local: Option<bool>,
pub language: Option<DbLanguage>,
} }
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)] #[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)]

View File

@ -1,4 +1,4 @@
use crate::{schema::local_user, LocalUserId, PersonId}; use crate::{schema::local_user, DbLanguage, LocalUserId, PersonId};
use serde::Serialize; use serde::Serialize;
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)] #[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
@ -12,11 +12,12 @@ pub struct LocalUser {
pub theme: String, pub theme: String,
pub default_sort_type: i16, pub default_sort_type: i16,
pub default_listing_type: i16, pub default_listing_type: i16,
pub lang: String, pub interface_language: String,
pub show_avatars: bool, pub show_avatars: bool,
pub send_notifications_to_email: bool, pub send_notifications_to_email: bool,
pub validator_time: chrono::NaiveDateTime, pub validator_time: chrono::NaiveDateTime,
pub show_scores: bool, pub show_scores: bool,
pub discussion_languages: Vec<DbLanguage>,
} }
// TODO redo these, check table defaults // TODO redo these, check table defaults
@ -30,10 +31,11 @@ pub struct LocalUserForm {
pub theme: Option<String>, pub theme: Option<String>,
pub default_sort_type: Option<i16>, pub default_sort_type: Option<i16>,
pub default_listing_type: Option<i16>, pub default_listing_type: Option<i16>,
pub lang: Option<String>, pub interface_language: Option<String>,
pub show_avatars: Option<bool>, pub show_avatars: Option<bool>,
pub send_notifications_to_email: Option<bool>, pub send_notifications_to_email: Option<bool>,
pub show_scores: Option<bool>, pub show_scores: Option<bool>,
pub discussion_languages: Option<Vec<DbLanguage>>,
} }
/// A local user view that removes password encrypted /// A local user view that removes password encrypted
@ -47,7 +49,7 @@ pub struct LocalUserSettings {
pub theme: String, pub theme: String,
pub default_sort_type: i16, pub default_sort_type: i16,
pub default_listing_type: i16, pub default_listing_type: i16,
pub lang: String, pub interface_language: String,
pub show_avatars: bool, pub show_avatars: bool,
pub send_notifications_to_email: bool, pub send_notifications_to_email: bool,
pub validator_time: chrono::NaiveDateTime, pub validator_time: chrono::NaiveDateTime,

View File

@ -1,6 +1,7 @@
use crate::{ use crate::{
schema::{post, post_like, post_read, post_saved}, schema::{post, post_like, post_read, post_saved},
CommunityId, CommunityId,
DbLanguage,
DbUrl, DbUrl,
PersonId, PersonId,
PostId, PostId,
@ -29,6 +30,7 @@ pub struct Post {
pub thumbnail_url: Option<DbUrl>, pub thumbnail_url: Option<DbUrl>,
pub ap_id: DbUrl, pub ap_id: DbUrl,
pub local: bool, pub local: bool,
pub language: DbLanguage,
} }
#[derive(Insertable, AsChangeset, Default)] #[derive(Insertable, AsChangeset, Default)]
@ -52,6 +54,7 @@ pub struct PostForm {
pub thumbnail_url: Option<DbUrl>, pub thumbnail_url: Option<DbUrl>,
pub ap_id: Option<DbUrl>, pub ap_id: Option<DbUrl>,
pub local: Option<bool>, pub local: Option<bool>,
pub language: Option<DbLanguage>,
} }
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]

View File

@ -1,4 +1,4 @@
use crate::{schema::private_message, DbUrl, PersonId, PrivateMessageId}; use crate::{schema::private_message, DbLanguage, DbUrl, PersonId, PrivateMessageId};
use serde::Serialize; use serde::Serialize;
#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)] #[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
@ -14,6 +14,7 @@ pub struct PrivateMessage {
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub ap_id: DbUrl, pub ap_id: DbUrl,
pub local: bool, pub local: bool,
pub language: DbLanguage,
} }
#[derive(Insertable, AsChangeset, Default)] #[derive(Insertable, AsChangeset, Default)]
@ -28,4 +29,5 @@ pub struct PrivateMessageForm {
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub ap_id: Option<DbUrl>, pub ap_id: Option<DbUrl>,
pub local: Option<bool>, pub local: Option<bool>,
pub language: Option<DbLanguage>,
} }

View File

@ -13,6 +13,7 @@ diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
log = "0.4.14" log = "0.4.14"
url = "2.2.1" url = "2.2.1"
language-tags = "0.3.0"
[dev-dependencies] [dev-dependencies]
serial_test = "0.5.1" serial_test = "0.5.1"

View File

@ -438,6 +438,7 @@ impl ViewToVec for CommentView {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::comment_view::*; use crate::comment_view::*;
use language_tags::LanguageTag;
use lemmy_db_queries::{ use lemmy_db_queries::{
aggregates::comment_aggregates::CommentAggregates, aggregates::comment_aggregates::CommentAggregates,
establish_unpooled_connection, establish_unpooled_connection,
@ -514,6 +515,7 @@ mod tests {
ap_id: inserted_comment.ap_id, ap_id: inserted_comment.ap_id,
updated: None, updated: None,
local: true, local: true,
language: LanguageTag::parse("en").unwrap().into(),
}, },
creator: PersonSafe { creator: PersonSafe {
id: inserted_person.id, id: inserted_person.id,
@ -554,6 +556,7 @@ mod tests {
thumbnail_url: None, thumbnail_url: None,
ap_id: inserted_post.ap_id.to_owned(), ap_id: inserted_post.ap_id.to_owned(),
local: true, local: true,
language: LanguageTag::parse("en").unwrap().into(),
}, },
community: CommunitySafe { community: CommunitySafe {
id: inserted_community.id, id: inserted_community.id,

View File

@ -432,6 +432,7 @@ impl ViewToVec for PostView {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::post_view::{PostQueryBuilder, PostView}; use crate::post_view::{PostQueryBuilder, PostView};
use language_tags::LanguageTag;
use lemmy_db_queries::{ use lemmy_db_queries::{
aggregates::post_aggregates::PostAggregates, aggregates::post_aggregates::PostAggregates,
establish_unpooled_connection, establish_unpooled_connection,
@ -535,6 +536,7 @@ mod tests {
thumbnail_url: None, thumbnail_url: None,
ap_id: inserted_post.ap_id.to_owned(), ap_id: inserted_post.ap_id.to_owned(),
local: true, local: true,
language: LanguageTag::parse("en").unwrap().into(),
}, },
my_vote: None, my_vote: None,
creator: PersonSafe { creator: PersonSafe {