Skip to content

Commit

Permalink
Merge pull request #1702 from scpwiki/user-accept-language
Browse files Browse the repository at this point in the history
Use user locale preferences in view, allow multiple locales
  • Loading branch information
emmiegit authored Nov 14, 2023
2 parents e623ac2 + c01950d commit 39c9ea6
Show file tree
Hide file tree
Showing 14 changed files with 133 additions and 66 deletions.
5 changes: 4 additions & 1 deletion deepwell/migrations/20220906103252_deepwell.sql
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ CREATE TABLE "user" (
password TEXT NOT NULL,
multi_factor_secret TEXT,
multi_factor_recovery_codes TEXT[],
locale TEXT NOT NULL,
locales TEXT[] NOT NULL,
avatar_s3_hash BYTEA,
real_name TEXT,
gender TEXT,
Expand All @@ -48,6 +48,9 @@ CREATE TABLE "user" (
-- Both MFA columns should either be set or unset
CHECK ((multi_factor_secret IS NULL) = (multi_factor_recovery_codes IS NULL)),

-- Locale must be unset for system users, but set for everyone else.
CHECK ((user_type = 'system' AND locales = '{}') OR (user_type != 'system' AND locales != '{}')),

-- Strings should either be NULL or non-empty (and within limits)
CHECK (real_name IS NULL OR (length(real_name) > 0 AND length(real_name) < 300)),
CHECK (gender IS NULL OR (length(gender) > 0 AND length(gender) < 100)),
Expand Down
10 changes: 5 additions & 5 deletions deepwell/seeder/users.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"slug": "administrator",
"email": "admin@wikijump",
"password": "wikijumpadmin1",
"locale": "en",
"locales": ["en"],
"real_name": "Administrator",
"gender": null,
"birthday": null,
Expand All @@ -27,7 +27,7 @@
"slug": "system",
"email": "system@wikijump",
"password": null,
"locale": "en",
"locales": [],
"real_name": "Wikijump",
"gender": null,
"birthday": "2019-01-18",
Expand All @@ -48,7 +48,7 @@
"slug": "anonymous",
"email": "anonymous@wikijump",
"password": null,
"locale": "en",
"locales": [],
"real_name": "Anonymous User",
"gender": null,
"birthday": null,
Expand All @@ -70,7 +70,7 @@
"slug": "unknown",
"email": "unknown@wikijump",
"password": null,
"locale": "en",
"locales": [],
"real_name": "Unknown User",
"gender": null,
"birthday": null,
Expand All @@ -90,7 +90,7 @@
"slug": "user",
"email": "user@wikijump",
"password": null,
"locale": "en",
"locales": ["en"],
"real_name": "Example User",
"gender": null,
"birthday": null,
Expand Down
2 changes: 1 addition & 1 deletion deepwell/src/database/seeder/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub struct User {
#[serde(rename = "type")]
pub user_type: UserType,
pub password: Option<String>,
pub locale: String,
pub locales: Vec<String>,
pub real_name: Option<String>,
pub gender: Option<String>,
pub birthday: Option<Date>,
Expand Down
2 changes: 1 addition & 1 deletion deepwell/src/database/seeder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub async fn seed(state: &ServerState) -> Result<()> {
name: user.name,
email: user.email,
password: user.password.unwrap_or_default(),
locale: user.locale,
locales: user.locales,
bypass_filter: true,
bypass_email_verification: true,
},
Expand Down
4 changes: 2 additions & 2 deletions deepwell/src/endpoints/user_bot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub async fn bot_user_create(
let CreateBotUser {
name,
email,
locale,
locales,
purpose,
owners,
authorization_token,
Expand All @@ -55,7 +55,7 @@ pub async fn bot_user_create(
user_type: UserType::Bot,
name,
email,
locale,
locales,
password: String::new(), // TODO configure user-bot password
bypass_filter,
bypass_email_verification,
Expand Down
3 changes: 1 addition & 2 deletions deepwell/src/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ pub struct Model {
#[sea_orm(column_type = "Text", nullable)]
pub multi_factor_secret: Option<String>,
pub multi_factor_recovery_codes: Option<Vec<String>>,
#[sea_orm(column_type = "Text")]
pub locale: String,
pub locales: Vec<String>,
#[sea_orm(column_type = "Binary(BlobSize::Blob(None))", nullable)]
pub avatar_s3_hash: Option<Vec<u8>>,
#[sea_orm(column_type = "Text", nullable)]
Expand Down
2 changes: 1 addition & 1 deletion deepwell/src/services/import/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ impl ImportService {
name: Set(name),
slug: Set(slug),
email: Set(email),
locale: Set(locale),
locales: Set(vec![locale]),
avatar_s3_hash: Set(avatar_s3_hash),
real_name: Set(real_name),
gender: Set(gender),
Expand Down
22 changes: 17 additions & 5 deletions deepwell/src/services/message/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::models::message_record::{
use crate::models::sea_orm_active_enums::{MessageRecipientType, UserType};
use crate::services::render::{RenderOutput, RenderService};
use crate::services::{InteractionService, TextService, UserService};
use crate::utils::validate_locale;
use cuid2::cuid;
use ftml::data::{PageInfo, ScoreValue};
use ftml::settings::{WikitextMode, WikitextSettings};
Expand All @@ -48,6 +49,7 @@ impl MessageService {
recipients,
carbon_copy,
blind_carbon_copy,
locale,
subject,
wikitext,
reply_to,
Expand All @@ -56,6 +58,9 @@ impl MessageService {
) -> Result<MessageDraftModel> {
info!("Creating message draft for user ID {user_id}");

// Check locale
validate_locale(&locale)?;

// Check foreign keys
if let Some(record_id) = &reply_to {
Self::check_message_access(ctx, record_id, user_id, "reply").await?;
Expand All @@ -76,6 +81,7 @@ impl MessageService {
recipients,
carbon_copy,
blind_carbon_copy,
locale,
subject,
wikitext,
reply_to: ProvidedValue::Set(reply_to),
Expand All @@ -96,16 +102,20 @@ impl MessageService {
recipients,
carbon_copy,
blind_carbon_copy,
locale,
subject,
wikitext,
}: UpdateMessageDraft,
) -> Result<MessageDraftModel> {
info!("Updating message draft {draft_id}");

// Validate parameters
validate_locale(&locale)?;

// Get current draft
let current_draft = Self::get_draft(ctx, &draft_id).await?;

// Update it
// Update the draft
let txn = ctx.transaction();
let draft = Self::draft_process(
ctx,
Expand All @@ -116,6 +126,7 @@ impl MessageService {
recipients,
carbon_copy,
blind_carbon_copy,
locale,
subject,
wikitext,
reply_to: ProvidedValue::Unset,
Expand All @@ -139,6 +150,7 @@ impl MessageService {
recipients,
carbon_copy,
blind_carbon_copy,
locale,
subject,
wikitext,
reply_to,
Expand All @@ -160,7 +172,6 @@ impl MessageService {
}

// Populate fields
let user = UserService::get(ctx, Reference::Id(user_id)).await?;
let recipients = serde_json::to_value(&recipients)?;

let wikitext_hash = TextService::create(ctx, wikitext.clone()).await?;
Expand All @@ -172,7 +183,7 @@ impl MessageService {
compiled_hash,
compiled_at,
compiled_generator,
} = Self::render(ctx, wikitext, &user.locale).await?;
} = Self::render(ctx, wikitext, &locale).await?;

Ok(message_draft::ActiveModel {
updated_at: Set(if is_update { Some(now()) } else { None }),
Expand Down Expand Up @@ -568,7 +579,7 @@ impl MessageService {
async fn render(
ctx: &ServiceContext<'_>,
wikitext: String,
user_locale: &str,
locale: &str,
) -> Result<RenderOutput> {
info!("Rendering message wikitext ({} bytes)", wikitext.len());

Expand All @@ -581,7 +592,7 @@ impl MessageService {
alt_title: None,
score: ScoreValue::Integer(0),
tags: vec![],
language: cow!(user_locale),
language: cow!(locale),
};

RenderService::render(ctx, wikitext, &page_info, &settings).await
Expand All @@ -597,6 +608,7 @@ struct DraftProcess {
recipients: Vec<i64>,
carbon_copy: Vec<i64>,
blind_carbon_copy: Vec<i64>,
locale: String,
subject: String,
wikitext: String,
reply_to: ProvidedValue<Option<String>>,
Expand Down
2 changes: 2 additions & 0 deletions deepwell/src/services/message/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub struct CreateMessageDraft {
pub recipients: Vec<i64>,
pub carbon_copy: Vec<i64>,
pub blind_carbon_copy: Vec<i64>,
pub locale: String,
pub subject: String,
pub wikitext: String,
pub reply_to: Option<String>,
Expand All @@ -36,6 +37,7 @@ pub struct UpdateMessageDraft {
pub recipients: Vec<i64>,
pub carbon_copy: Vec<i64>,
pub blind_carbon_copy: Vec<i64>,
pub locale: String,
pub subject: String,
pub wikitext: String,
}
Expand Down
4 changes: 2 additions & 2 deletions deepwell/src/services/site/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl SiteService {
user_type: UserType::Site,
name: format!("site:{slug}"),
email: String::new(),
locale,
locales: vec![locale],
password: String::new(),
bypass_filter: false,
bypass_email_verification: false,
Expand Down Expand Up @@ -153,7 +153,7 @@ impl SiteService {
if let ProvidedValue::Set(locale) = input.locale {
validate_locale(&locale)?;
model.locale = Set(locale.clone());
site_user_body.locale = ProvidedValue::Set(locale);
site_user_body.locales = ProvidedValue::Set(vec![locale]);
}

// Update site
Expand Down
45 changes: 41 additions & 4 deletions deepwell/src/services/user/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl UserService {
user_type,
mut name,
email,
locale,
locales,
password,
bypass_filter,
bypass_email_verification,
Expand Down Expand Up @@ -84,6 +84,9 @@ impl UserService {
)?;
}

// Validate locales for this type
Self::validate_locales(user_type, &locales)?;

// Check for name conflicts
let result = User::find()
.filter(
Expand Down Expand Up @@ -216,7 +219,7 @@ impl UserService {
password: Set(password),
multi_factor_secret: Set(None),
multi_factor_recovery_codes: Set(None),
locale: Set(locale),
locales: Set(locales),
avatar_s3_hash: Set(None),
real_name: Set(None),
gender: Set(None),
Expand Down Expand Up @@ -389,8 +392,9 @@ impl UserService {
model.password = Set(password_hash);
}

if let ProvidedValue::Set(locale) = input.locale {
model.locale = Set(locale);
if let ProvidedValue::Set(locales) = input.locales {
Self::validate_locales(user.user_type, &locales)?;
model.locales = Set(locales);
}

if let ProvidedValue::Set(real_name) = input.real_name {
Expand Down Expand Up @@ -693,6 +697,39 @@ impl UserService {
filter_matcher.verify(ctx, email).await?;
Ok(())
}

fn validate_locales<S: AsRef<str>>(user_type: UserType, locales: &[S]) -> Result<()> {
use crate::utils::validate_locale;

debug!(
"Validating locales ({}) for user type {:?}",
locales.len(),
user_type,
);

// Ensure values are valid
for locale in locales {
validate_locale(locale.as_ref())?;
}

// Invariants for locale lists
let valid = match user_type {
// System users should have no locales set
UserType::System => locales.is_empty(),

// Site users should have one locale set
UserType::Site => locales.len() == 1,

// Regular, should have a nonzero number of locales
_ => !locales.is_empty(),
};

if valid {
Ok(())
} else {
Err(Error::BadRequest)
}
}
}

fn get_user_slug(name: &str, user_type: UserType) -> String {
Expand Down
4 changes: 2 additions & 2 deletions deepwell/src/services/user/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub struct CreateUser {
pub user_type: UserType,
pub name: String,
pub email: String,
pub locale: String,
pub locales: Vec<String>,
pub password: String,

#[serde(default)]
Expand Down Expand Up @@ -72,7 +72,7 @@ pub struct UpdateUserBody {
pub email: ProvidedValue<String>,
pub email_verified: ProvidedValue<bool>,
pub password: ProvidedValue<String>,
pub locale: ProvidedValue<String>,
pub locales: ProvidedValue<Vec<String>>,
pub avatar: ProvidedValue<Option<Bytes<'static>>>,
pub real_name: ProvidedValue<Option<String>>,
pub gender: ProvidedValue<Option<String>>,
Expand Down
2 changes: 1 addition & 1 deletion deepwell/src/services/user_bot_owner/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::web::Reference;
pub struct CreateBotUser {
pub name: String,
pub email: String,
pub locale: String,
pub locales: Vec<String>,
pub purpose: String,
pub owners: Vec<BotOwner>,
pub bypass_filter: bool,
Expand Down
Loading

0 comments on commit 39c9ea6

Please sign in to comment.