From 418d31a127c0eec0605fe041621f453f0def7e23 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Wed, 26 Mar 2025 01:49:02 +0100 Subject: [PATCH] * Support for lang in URL as /fr/recipe/view/42 * Create a pages module in the frontend crate --- Cargo.lock | 51 ++++++------ TODO.md | 4 +- backend/src/main.rs | 105 ++++++++++++++---------- backend/src/services/recipe.rs | 2 +- deploy.nu | 2 - frontend/src/lib.rs | 18 ++-- frontend/src/{ => pages}/home.rs | 0 frontend/src/pages/mod.rs | 3 + frontend/src/{ => pages}/recipe_edit.rs | 0 frontend/src/{ => pages}/recipe_view.rs | 0 frontend/src/recipe_scheduler.rs | 2 +- frontend/src/utils.rs | 10 +++ 12 files changed, 117 insertions(+), 80 deletions(-) rename frontend/src/{ => pages}/home.rs (100%) create mode 100644 frontend/src/pages/mod.rs rename frontend/src/{ => pages}/recipe_edit.rs (100%) rename frontend/src/{ => pages}/recipe_view.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index ccf8b95..5d3c8fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,9 +330,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.16" +version = "1.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ "shlex", ] @@ -537,9 +537,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" dependencies = [ "powerfmt", ] @@ -1226,14 +1226,15 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -1512,9 +1513,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "matchers" @@ -1829,7 +1830,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.23", + "zerocopy 0.8.24", ] [[package]] @@ -1906,7 +1907,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.23", + "zerocopy 0.8.24", ] [[package]] @@ -2165,9 +2166,9 @@ checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" -version = "0.103.0" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "ring", "rustls-pki-types", @@ -2664,9 +2665,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", "getrandom 0.3.2", @@ -2727,9 +2728,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.40" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -2748,9 +2749,9 @@ checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -3194,9 +3195,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -3462,11 +3463,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "zerocopy-derive 0.8.23", + "zerocopy-derive 0.8.24", ] [[package]] @@ -3482,9 +3483,9 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", diff --git a/TODO.md b/TODO.md index a4aab03..899e215 100644 --- a/TODO.md +++ b/TODO.md @@ -8,13 +8,15 @@ * Default number is the user setting user.default_servings * A symbol show the native recipe servings number * Check position of message error in profile/sign in/sign up with flex grid layout +* Replace Rinja by Askama when Askma 0.13 is out (Rinja has been merged with Askama) * Define the UI (mockups). * Two CSS: one for desktop and one for mobile * Use CSS flex/grid to define a good design/layout * CSS for dark mode + autodetect * CSS for toast and modal dialog * Calendar: Choose the first day of the week -* i18n: prefix uri with the language: /fr/recipe/view/2 +* i18n: prefix uri with the language: /fr/recipe/view/2 (do it for all intern href) + * Redirect with the correct prefix when the current language is changed * Make a search page Use FTS5: https://sqlite.org/fts5.html diff --git a/backend/src/main.rs b/backend/src/main.rs index efee562..06ff7c4 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,10 +1,10 @@ use std::{net::SocketAddr, path::Path}; use axum::{ - BoxError, Router, + BoxError, Router, ServiceExt, error_handling::HandleErrorLayer, extract::{ConnectInfo, Extension, FromRef, Request, State}, - http::StatusCode, + http::{StatusCode, Uri}, middleware::{self, Next}, response::Response, routing::{delete, get, patch, post, put}, @@ -14,6 +14,7 @@ use chrono::prelude::*; use clap::Parser; use config::Config; use itertools::Itertools; +use tower::layer::Layer; use tower::{ServiceBuilder, buffer::BufferLayer, limit::RateLimitLayer}; use tower_http::{ services::{ServeDir, ServeFile}, @@ -290,13 +291,20 @@ async fn main() { .with_state(state) .nest_service("/favicon.ico", ServeFile::new("static/favicon.ico")) .nest_service("/static", ServeDir::new("static")) - .layer(TraceLayer::new_for_http()) - .into_make_service_with_connect_info::(); + .layer(TraceLayer::new_for_http()); + + let url_rewriting_middleware = tower::util::MapRequestLayer::new(url_rewriting); + let app_with_url_rewriting = url_rewriting_middleware.layer(app); let addr = SocketAddr::from(([0, 0, 0, 0], port)); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - axum::serve(listener, app).await.unwrap(); + axum::serve( + listener, + app_with_url_rewriting.into_make_service_with_connect_info::(), + ) + .await + .unwrap(); } async fn user_authentication( @@ -312,54 +320,65 @@ async fn user_authentication( Ok(next.run(req).await) } +#[derive(Debug, Clone)] +struct Lang(Option); + +fn url_rewriting(mut req: Request) -> Request { + // Here we are extracting the language from the url then rewriting it. + // For example: + // "/fr/recipe/view/1" + // lang = "fr" and uri rewritten as = "/recipe/view/1" + let lang_and_new_uri = 'lang_and_new_uri: { + if let Some(path_query) = req.uri().path_and_query() { + let mut parts = path_query.path().split('/'); + let _ = parts.next(); // Empty part due to the first '/'. + if let Some(lang) = parts.next() { + let available_codes = translation::available_codes(); + if available_codes.contains(&lang) { + let mut rest: String = String::from(""); + for part in parts { + rest.push('/'); + rest.push_str(part); + } + if let Some(query) = path_query.query() { + rest.push('?'); + rest.push_str(query); + } + + if let Ok(new_uri) = rest.parse::() { + break 'lang_and_new_uri Some((lang.to_string(), new_uri)); + } + } + } + } + None + }; + + if let Some((lang, new_uri)) = lang_and_new_uri { + *req.uri_mut() = new_uri; + req.extensions_mut().insert(Lang(Some(lang))); + } else { + req.extensions_mut().insert(Lang(None)); + } + + req +} + /// The language of the current HTTP request is defined in the current order: -/// - Extraction from the url: like in '/fr/recipe/view/42' (Not yet implemented). +/// - Extraction from the url: like in '/fr/recipe/view/42' /// - Get from the user database record. /// - Get from the cookie. /// - Get from the HTTP header `accept-language`. /// - Set as `translation::DEFAULT_LANGUAGE_CODE`. async fn translation( + Extension(lang): Extension, Extension(user): Extension>, mut req: Request, next: Next, ) -> Result { - // Here we are extracting the language from the url then rewriting it. - // For example: - // "/fr/recipe/view/1" - // lang = "fr" and uri rewritten as = "/recipe/view/1" - // Disable because it doesn't work at this level, see: - // https://docs.rs/axum/latest/axum/middleware/index.html#rewriting-request-uri-in-middleware - - // let lang_and_new_uri = 'lang_from_uri: { - // if let Some(path_query) = req.uri().path_and_query() { - // event!(Level::INFO, "path: {:?}", path_query.path()); - // let mut parts = path_query.path().split('/'); - // let _ = parts.next(); // Empty part due to the first '/'. - // if let Some(lang) = parts.next() { - // let available_codes = translation::available_codes(); - // if available_codes.contains(&lang) { - // let mut rest: String = String::from(""); - // for part in parts { - // rest.push('/'); - // rest.push_str(part); - // } - // // let uri_builder = Uri::builder() - // if let Ok(new_uri) = rest.parse::() { - // event!(Level::INFO, "path rewrite: {:?}", new_uri.path()); - // break 'lang_from_uri Some((lang.to_string(), new_uri)); - // } - // } - // } - // } - // None - // }; - // let language = if let Some((lang, uri)) = lang_and_new_uri { - // *req.uri_mut() = uri; // Replace the URI without the language. - // event!(Level::INFO, "URI: {:?}", req.uri()); - // lang - // } else - - let language = if let Some(user) = user { + let language = if let Some(lang) = lang.0 { + lang + } else if let Some(user) = user { user.lang } else { let available_codes = translation::available_codes(); diff --git a/backend/src/services/recipe.rs b/backend/src/services/recipe.rs index 799ffee..9f5c1f2 100644 --- a/backend/src/services/recipe.rs +++ b/backend/src/services/recipe.rs @@ -7,10 +7,10 @@ use rinja::Template; // use tracing::{event, Level}; use crate::{ + Result, data::{db, model}, html_templates::*, translation::{self, Sentence}, - Result, }; #[debug_handler] diff --git a/deploy.nu b/deploy.nu index 4fe6404..3972ce0 100644 --- a/deploy.nu +++ b/deploy.nu @@ -18,8 +18,6 @@ def main [host: string, destination: string, ssh_key: path] { } cd frontend - # source frontend/deploy.nu - # main true trunk build --release cd .. diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index 0323e37..11dba8d 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -9,12 +9,10 @@ use crate::utils::selector; mod calendar; mod error; -mod home; mod modal_dialog; mod on_click; -mod recipe_edit; +mod pages; mod recipe_scheduler; -mod recipe_view; mod request; mod shopping_list; mod toast; @@ -24,8 +22,14 @@ mod utils; pub fn main() -> Result<(), JsValue> { console_error_panic_hook::set_once(); + let lang = utils::get_current_lang(); + let location = window().location().pathname()?; - let path: Vec<&str> = location.split('/').skip(1).collect(); + let path: Vec<&str> = location + .split('/') + .skip(1) + .skip_while(|part| *part == lang) + .collect(); let is_user_logged = selector::("html") .dataset() @@ -36,14 +40,14 @@ pub fn main() -> Result<(), JsValue> { match path[..] { ["recipe", "edit", id] => { let id = id.parse::().unwrap(); // TODO: remove unwrap. - recipe_edit::setup_page(id) + pages::recipe_edit::setup_page(id) } ["recipe", "view", id] => { let id = id.parse::().unwrap(); // TODO: remove unwrap. - recipe_view::setup_page(id, is_user_logged) + pages::recipe_view::setup_page(id, is_user_logged) } // Home. - [""] => home::setup_page(is_user_logged), + [""] => pages::home::setup_page(is_user_logged), _ => log!("Path unknown: ", location), } diff --git a/frontend/src/home.rs b/frontend/src/pages/home.rs similarity index 100% rename from frontend/src/home.rs rename to frontend/src/pages/home.rs diff --git a/frontend/src/pages/mod.rs b/frontend/src/pages/mod.rs new file mode 100644 index 0000000..2db264d --- /dev/null +++ b/frontend/src/pages/mod.rs @@ -0,0 +1,3 @@ +pub mod home; +pub mod recipe_edit; +pub mod recipe_view; diff --git a/frontend/src/recipe_edit.rs b/frontend/src/pages/recipe_edit.rs similarity index 100% rename from frontend/src/recipe_edit.rs rename to frontend/src/pages/recipe_edit.rs diff --git a/frontend/src/recipe_view.rs b/frontend/src/pages/recipe_view.rs similarity index 100% rename from frontend/src/recipe_view.rs rename to frontend/src/pages/recipe_view.rs diff --git a/frontend/src/recipe_scheduler.rs b/frontend/src/recipe_scheduler.rs index f9789fd..c089afb 100644 --- a/frontend/src/recipe_scheduler.rs +++ b/frontend/src/recipe_scheduler.rs @@ -1,7 +1,7 @@ use chrono::{Datelike, Days, Months, NaiveDate}; use common::ron_api; use gloo::storage::{LocalStorage, Storage}; -use ron::ser::{to_string_pretty, PrettyConfig}; +use ron::ser::{PrettyConfig, to_string_pretty}; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/frontend/src/utils.rs b/frontend/src/utils.rs index ac347a2..3856111 100644 --- a/frontend/src/utils.rs +++ b/frontend/src/utils.rs @@ -99,6 +99,16 @@ where .unwrap() } +pub fn get_current_lang() -> String { + selector::("html") + .get_attribute("lang") + .unwrap() + .split("-") + .next() + .unwrap() + .to_string() +} + pub fn get_locale() -> Locale { let lang_and_territory = selector::("html") .get_attribute("lang") -- 2.49.0