From f2f33518244009d6209d9e2d0b55ffc86e51c917 Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 15:13:12 -0500 Subject: [PATCH 01/10] Get site from session first, before domain. This way we can get user locale, if present. --- deepwell/src/services/view/service.rs | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/deepwell/src/services/view/service.rs b/deepwell/src/services/view/service.rs index 17de211c7d..aa443a110c 100644 --- a/deepwell/src/services/view/service.rs +++ b/deepwell/src/services/view/service.rs @@ -369,6 +369,22 @@ impl ViewService { ) -> Result { info!("Getting viewer data from domain '{domain}' and session token"); + // Get user data from session token (if present) + let user_session = match session_token { + None => None, + Some(token) if token.is_empty() => None, + Some(token) => { + let session = SessionService::get(ctx, token).await?; + let user = UserService::get(ctx, Reference::Id(session.user_id)).await?; + + Some(UserSession { + session, + user, + user_permissions: UserPermissions, // TODO add user permissions, get scheme for user and site + }) + } + }; + // Get site data let (site, redirect_site) = match DomainService::parse_site_from_domain(ctx, domain).await? { @@ -391,22 +407,6 @@ impl ViewService { } }; - // Get user data from session token (if present) - let user_session = match session_token { - None => None, - Some(token) if token.is_empty() => None, - Some(token) => { - let session = SessionService::get(ctx, token).await?; - let user = UserService::get(ctx, Reference::Id(session.user_id)).await?; - - Some(UserSession { - session, - user, - user_permissions: UserPermissions, // TODO add user permissions, get scheme for user and site - }) - } - }; - Ok(ViewerResult::FoundSite(Viewer { site, redirect_site, From 488d9df921ace288a166a5f6741bf1c1acbc055d Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 15:25:12 -0500 Subject: [PATCH 02/10] Modify locale list to copy user locale preferences. --- deepwell/src/services/view/service.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/deepwell/src/services/view/service.rs b/deepwell/src/services/view/service.rs index aa443a110c..6d0348d010 100644 --- a/deepwell/src/services/view/service.rs +++ b/deepwell/src/services/view/service.rs @@ -68,7 +68,7 @@ impl ViewService { ); // Parse all locales - let locales = parse_locales(&locales_str)?; + let mut locales = parse_locales(&locales_str)?; // Attempt to get a viewer helper structure, but if the site doesn't exist // then return right away with the "no such site" response. @@ -78,7 +78,7 @@ impl ViewService { user_session, } = match Self::get_viewer( ctx, - &locales, + &mut locales, &domain, session_token.ref_map(|s| s.as_str()), ) @@ -303,7 +303,7 @@ impl ViewService { ); // Parse all locales - let locales = parse_locales(&locales_str)?; + let mut locales = parse_locales(&locales_str)?; // Attempt to get a viewer helper structure, but if the site doesn't exist // then return right away with the "no such site" response. @@ -313,7 +313,7 @@ impl ViewService { user_session, } = match Self::get_viewer( ctx, - &locales, + &mut locales, &domain, session_token.ref_map(|s| s.as_str()), ) @@ -363,7 +363,7 @@ impl ViewService { /// operations, such as slug normalization or redirect site aliases. pub async fn get_viewer( ctx: &ServiceContext<'_>, - locales: &[LanguageIdentifier], + locales: &mut Vec, domain: &str, session_token: Option<&str>, ) -> Result { @@ -377,6 +377,13 @@ impl ViewService { let session = SessionService::get(ctx, token).await?; let user = UserService::get(ctx, Reference::Id(session.user_id)).await?; + // If no locales specified, then use whatever the user has set + // TODO get locale priority list from settings + if locales.is_empty() { + let locale = LanguageIdentifier::from_bytes(user.locale.as_bytes())?; + locales.push(locale); + } + Some(UserSession { session, user, @@ -500,14 +507,9 @@ impl ViewService { /// Converts an array of strings to a list of locales. /// -/// # Errors -/// If the input array is empty. +/// Empty locales lists _are_ allowed, since we have not +/// yet checked the user's locale preferences. fn parse_locales>(locales_str: &[S]) -> Result> { - if locales_str.is_empty() { - warn!("List of locales is empty"); - return Err(Error::NoLocalesSpecified); - } - let mut locales = Vec::with_capacity(locales_str.len()); for locale_str in locales_str { let locale = LanguageIdentifier::from_bytes(locale_str.as_ref().as_bytes())?; From f2b68aafefaa4aefe972ce0edba79a46a64e4dc4 Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 15:34:59 -0500 Subject: [PATCH 03/10] Avoid deconstructing Viewer unnecessary. --- deepwell/src/services/view/service.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/deepwell/src/services/view/service.rs b/deepwell/src/services/view/service.rs index 6d0348d010..a8b17a9f0c 100644 --- a/deepwell/src/services/view/service.rs +++ b/deepwell/src/services/view/service.rs @@ -307,11 +307,7 @@ impl ViewService { // Attempt to get a viewer helper structure, but if the site doesn't exist // then return right away with the "no such site" response. - let Viewer { - site, - redirect_site, - user_session, - } = match Self::get_viewer( + let viewer = match Self::get_viewer( ctx, &mut locales, &domain, @@ -327,12 +323,6 @@ impl ViewService { // TODO Check if user-agent and IP match? - let viewer = Viewer { - site, - redirect_site, - user_session, - }; - // Get data to return for this user. let user = match user_ref { Some(user_ref) => UserService::get_optional(ctx, user_ref).await?, From 0fb79a82b3f30fd17efc276b53ee25f7b6001a97 Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 15:40:30 -0500 Subject: [PATCH 04/10] Change user.locale column to locales (TEXT[]). --- deepwell/migrations/20220906103252_deepwell.sql | 2 +- deepwell/src/models/user.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/deepwell/migrations/20220906103252_deepwell.sql b/deepwell/migrations/20220906103252_deepwell.sql index 767097509e..d21fa3d619 100644 --- a/deepwell/migrations/20220906103252_deepwell.sql +++ b/deepwell/migrations/20220906103252_deepwell.sql @@ -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, diff --git a/deepwell/src/models/user.rs b/deepwell/src/models/user.rs index 5e21ae0b76..323d5577f5 100644 --- a/deepwell/src/models/user.rs +++ b/deepwell/src/models/user.rs @@ -29,8 +29,7 @@ pub struct Model { #[sea_orm(column_type = "Text", nullable)] pub multi_factor_secret: Option, pub multi_factor_recovery_codes: Option>, - #[sea_orm(column_type = "Text")] - pub locale: String, + pub locales: Vec, #[sea_orm(column_type = "Binary(BlobSize::Blob(None))", nullable)] pub avatar_s3_hash: Option>, #[sea_orm(column_type = "Text", nullable)] From ba40ee2933f2d6741c3a001ec35e74da39ac56ca Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 15:52:20 -0500 Subject: [PATCH 05/10] Update locale -> locales in seeder JSON. --- deepwell/seeder/users.json | 10 +++++----- deepwell/src/database/seeder/data.rs | 2 +- deepwell/src/database/seeder/mod.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/deepwell/seeder/users.json b/deepwell/seeder/users.json index 28301e2919..8739656e84 100644 --- a/deepwell/seeder/users.json +++ b/deepwell/seeder/users.json @@ -6,7 +6,7 @@ "slug": "administrator", "email": "admin@wikijump", "password": "wikijumpadmin1", - "locale": "en", + "locales": ["en"], "real_name": "Administrator", "gender": null, "birthday": null, @@ -27,7 +27,7 @@ "slug": "system", "email": "system@wikijump", "password": null, - "locale": "en", + "locales": [], "real_name": "Wikijump", "gender": null, "birthday": "2019-01-18", @@ -48,7 +48,7 @@ "slug": "anonymous", "email": "anonymous@wikijump", "password": null, - "locale": "en", + "locales": [], "real_name": "Anonymous User", "gender": null, "birthday": null, @@ -70,7 +70,7 @@ "slug": "unknown", "email": "unknown@wikijump", "password": null, - "locale": "en", + "locales": [], "real_name": "Unknown User", "gender": null, "birthday": null, @@ -90,7 +90,7 @@ "slug": "user", "email": "user@wikijump", "password": null, - "locale": "en", + "locales": ["en"], "real_name": "Example User", "gender": null, "birthday": null, diff --git a/deepwell/src/database/seeder/data.rs b/deepwell/src/database/seeder/data.rs index 2c7b1ffe0d..4dd4f41873 100644 --- a/deepwell/src/database/seeder/data.rs +++ b/deepwell/src/database/seeder/data.rs @@ -91,7 +91,7 @@ pub struct User { #[serde(rename = "type")] pub user_type: UserType, pub password: Option, - pub locale: String, + pub locales: Vec, pub real_name: Option, pub gender: Option, pub birthday: Option, diff --git a/deepwell/src/database/seeder/mod.rs b/deepwell/src/database/seeder/mod.rs index 3456ce9d5a..8f75e031b0 100644 --- a/deepwell/src/database/seeder/mod.rs +++ b/deepwell/src/database/seeder/mod.rs @@ -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, }, From d4e7e32f3d1c51a3091461820497c4ceece2198a Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 16:06:57 -0500 Subject: [PATCH 06/10] Update logic from user.locale -> locales. --- deepwell/src/endpoints/user_bot.rs | 4 ++-- deepwell/src/services/import/service.rs | 2 +- deepwell/src/services/message/service.rs | 22 ++++++++++++++----- deepwell/src/services/message/structs.rs | 2 ++ deepwell/src/services/site/service.rs | 4 ++-- deepwell/src/services/user/service.rs | 8 +++---- deepwell/src/services/user/structs.rs | 4 ++-- .../src/services/user_bot_owner/structs.rs | 2 +- deepwell/src/services/view/service.rs | 5 ++--- 9 files changed, 33 insertions(+), 20 deletions(-) diff --git a/deepwell/src/endpoints/user_bot.rs b/deepwell/src/endpoints/user_bot.rs index f1ee36222f..2d980e592f 100644 --- a/deepwell/src/endpoints/user_bot.rs +++ b/deepwell/src/endpoints/user_bot.rs @@ -35,7 +35,7 @@ pub async fn bot_user_create( let CreateBotUser { name, email, - locale, + locales, purpose, owners, authorization_token, @@ -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, diff --git a/deepwell/src/services/import/service.rs b/deepwell/src/services/import/service.rs index fb43cdd5f2..83bfb73f77 100644 --- a/deepwell/src/services/import/service.rs +++ b/deepwell/src/services/import/service.rs @@ -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), diff --git a/deepwell/src/services/message/service.rs b/deepwell/src/services/message/service.rs index 0f55824ca6..c73b9e9429 100644 --- a/deepwell/src/services/message/service.rs +++ b/deepwell/src/services/message/service.rs @@ -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}; @@ -48,6 +49,7 @@ impl MessageService { recipients, carbon_copy, blind_carbon_copy, + locale, subject, wikitext, reply_to, @@ -56,6 +58,9 @@ impl MessageService { ) -> Result { 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?; @@ -76,6 +81,7 @@ impl MessageService { recipients, carbon_copy, blind_carbon_copy, + locale, subject, wikitext, reply_to: ProvidedValue::Set(reply_to), @@ -96,16 +102,20 @@ impl MessageService { recipients, carbon_copy, blind_carbon_copy, + locale, subject, wikitext, }: UpdateMessageDraft, ) -> Result { 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, @@ -116,6 +126,7 @@ impl MessageService { recipients, carbon_copy, blind_carbon_copy, + locale, subject, wikitext, reply_to: ProvidedValue::Unset, @@ -139,6 +150,7 @@ impl MessageService { recipients, carbon_copy, blind_carbon_copy, + locale, subject, wikitext, reply_to, @@ -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?; @@ -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 }), @@ -568,7 +579,7 @@ impl MessageService { async fn render( ctx: &ServiceContext<'_>, wikitext: String, - user_locale: &str, + locale: &str, ) -> Result { info!("Rendering message wikitext ({} bytes)", wikitext.len()); @@ -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 @@ -597,6 +608,7 @@ struct DraftProcess { recipients: Vec, carbon_copy: Vec, blind_carbon_copy: Vec, + locale: String, subject: String, wikitext: String, reply_to: ProvidedValue>, diff --git a/deepwell/src/services/message/structs.rs b/deepwell/src/services/message/structs.rs index 07949c1036..68043069fc 100644 --- a/deepwell/src/services/message/structs.rs +++ b/deepwell/src/services/message/structs.rs @@ -24,6 +24,7 @@ pub struct CreateMessageDraft { pub recipients: Vec, pub carbon_copy: Vec, pub blind_carbon_copy: Vec, + pub locale: String, pub subject: String, pub wikitext: String, pub reply_to: Option, @@ -36,6 +37,7 @@ pub struct UpdateMessageDraft { pub recipients: Vec, pub carbon_copy: Vec, pub blind_carbon_copy: Vec, + pub locale: String, pub subject: String, pub wikitext: String, } diff --git a/deepwell/src/services/site/service.rs b/deepwell/src/services/site/service.rs index 8bbd1a73d6..eaa24981ce 100644 --- a/deepwell/src/services/site/service.rs +++ b/deepwell/src/services/site/service.rs @@ -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, @@ -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 diff --git a/deepwell/src/services/user/service.rs b/deepwell/src/services/user/service.rs index a3956fe257..eb93851ea2 100644 --- a/deepwell/src/services/user/service.rs +++ b/deepwell/src/services/user/service.rs @@ -45,7 +45,7 @@ impl UserService { user_type, mut name, email, - locale, + locales, password, bypass_filter, bypass_email_verification, @@ -216,7 +216,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), @@ -389,8 +389,8 @@ 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 { + model.locales = Set(locales); } if let ProvidedValue::Set(real_name) = input.real_name { diff --git a/deepwell/src/services/user/structs.rs b/deepwell/src/services/user/structs.rs index e67d58c65b..67ed08d7f0 100644 --- a/deepwell/src/services/user/structs.rs +++ b/deepwell/src/services/user/structs.rs @@ -30,7 +30,7 @@ pub struct CreateUser { pub user_type: UserType, pub name: String, pub email: String, - pub locale: String, + pub locales: Vec, pub password: String, #[serde(default)] @@ -72,7 +72,7 @@ pub struct UpdateUserBody { pub email: ProvidedValue, pub email_verified: ProvidedValue, pub password: ProvidedValue, - pub locale: ProvidedValue, + pub locales: ProvidedValue>, pub avatar: ProvidedValue>>, pub real_name: ProvidedValue>, pub gender: ProvidedValue>, diff --git a/deepwell/src/services/user_bot_owner/structs.rs b/deepwell/src/services/user_bot_owner/structs.rs index 1cf3e27109..16da99dd59 100644 --- a/deepwell/src/services/user_bot_owner/structs.rs +++ b/deepwell/src/services/user_bot_owner/structs.rs @@ -25,7 +25,7 @@ use crate::web::Reference; pub struct CreateBotUser { pub name: String, pub email: String, - pub locale: String, + pub locales: Vec, pub purpose: String, pub owners: Vec, pub bypass_filter: bool, diff --git a/deepwell/src/services/view/service.rs b/deepwell/src/services/view/service.rs index a8b17a9f0c..30851f5df2 100644 --- a/deepwell/src/services/view/service.rs +++ b/deepwell/src/services/view/service.rs @@ -368,10 +368,9 @@ impl ViewService { let user = UserService::get(ctx, Reference::Id(session.user_id)).await?; // If no locales specified, then use whatever the user has set - // TODO get locale priority list from settings if locales.is_empty() { - let locale = LanguageIdentifier::from_bytes(user.locale.as_bytes())?; - locales.push(locale); + let mut user_locales = parse_locales(&user.locales)?; + locales.append(&mut user_locales); } Some(UserSession { From 0d151c2ada0bfedff5df828f15dca4617c4b277d Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 16:14:30 -0500 Subject: [PATCH 07/10] Add empty locale check. --- deepwell/migrations/20220906103252_deepwell.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deepwell/migrations/20220906103252_deepwell.sql b/deepwell/migrations/20220906103252_deepwell.sql index d21fa3d619..cd9be7fe37 100644 --- a/deepwell/migrations/20220906103252_deepwell.sql +++ b/deepwell/migrations/20220906103252_deepwell.sql @@ -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)), From 704b76f00b160f12bf1e6c7a3972cf9852371eb4 Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 16:24:56 -0500 Subject: [PATCH 08/10] Validate locales for user settings. --- deepwell/src/services/user/service.rs | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/deepwell/src/services/user/service.rs b/deepwell/src/services/user/service.rs index eb93851ea2..e93dd91fa6 100644 --- a/deepwell/src/services/user/service.rs +++ b/deepwell/src/services/user/service.rs @@ -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( @@ -390,6 +393,7 @@ impl UserService { } if let ProvidedValue::Set(locales) = input.locales { + Self::validate_locales(user.user_type, &locales)?; model.locales = Set(locales); } @@ -693,6 +697,39 @@ impl UserService { filter_matcher.verify(ctx, email).await?; Ok(()) } + + fn validate_locales>(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 { From cdaeccc6539b1a86d9ab69cad6212d37356049b6 Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 16:35:13 -0500 Subject: [PATCH 09/10] Insert all user locales to the front of locales, always. This way we always prefer the user's settings over the browser's request. --- deepwell/src/services/view/service.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/deepwell/src/services/view/service.rs b/deepwell/src/services/view/service.rs index 30851f5df2..983a42bffe 100644 --- a/deepwell/src/services/view/service.rs +++ b/deepwell/src/services/view/service.rs @@ -46,6 +46,7 @@ use ftml::prelude::*; use ftml::render::html::HtmlOutput; use ref_map::*; use std::borrow::Cow; +use std::mem; use unic_langid::LanguageIdentifier; use wikidot_normalize::normalize; @@ -367,10 +368,26 @@ impl ViewService { let session = SessionService::get(ctx, token).await?; let user = UserService::get(ctx, Reference::Id(session.user_id)).await?; - // If no locales specified, then use whatever the user has set - if locales.is_empty() { + // Prefer what the user has set over what the browser is requesting + { + // Get the list of user locales + // + // Our goal is to insert this list of user locales at the front. + // For instance, if the browser is requesting [X, Y], but the user + // prefers [A, B], we want to end up with [A, B, X, Y]. + // + // But the most efficient method to use here is append(). + // So we append all the requested locales to the end of the user + // locales we just got, then swap the contents. + // + // The end goal is that 'locales' ends up with the new locales at + // the start before the previous items, and 'user_locales' ends up + // drained since it was inserted into the preserved 'locales' vector. + let mut user_locales = parse_locales(&user.locales)?; - locales.append(&mut user_locales); + user_locales.append(locales); + mem::swap(locales, &mut user_locales); + debug_assert!(user_locales.is_empty()); } Some(UserSession { From c01950df7b74a46c7c96a7071e65a820cbcdd100 Mon Sep 17 00:00:00 2001 From: Emmie Maeda Date: Mon, 13 Nov 2023 16:39:41 -0500 Subject: [PATCH 10/10] Add empty locale check, since it's no longer in parse_locales(). --- deepwell/src/services/view/service.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deepwell/src/services/view/service.rs b/deepwell/src/services/view/service.rs index 983a42bffe..a7ff249499 100644 --- a/deepwell/src/services/view/service.rs +++ b/deepwell/src/services/view/service.rs @@ -398,6 +398,12 @@ impl ViewService { } }; + // Ensure at least one locale was requested + if locales.is_empty() { + error!("No locales specified in user settings or Accept-Language header"); + return Err(Error::NoLocalesSpecified); + } + // Get site data let (site, redirect_site) = match DomainService::parse_site_from_domain(ctx, domain).await? {