Update dependencies, add translation endpoint, and display a user message when logged in
This commit is contained in:
parent
6b043620b8
commit
348b0f24e9
19 changed files with 170 additions and 34 deletions
27
Cargo.lock
generated
27
Cargo.lock
generated
|
|
@ -166,7 +166,7 @@ dependencies = [
|
|||
"memchr",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"winnow 0.7.8",
|
||||
"winnow 0.7.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -499,9 +499,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.20"
|
||||
version = "1.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a"
|
||||
checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
|
@ -592,13 +592,14 @@ dependencies = [
|
|||
"chrono",
|
||||
"ron",
|
||||
"serde",
|
||||
"strum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "comrak"
|
||||
version = "0.38.0"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f690706b5db081dccea6206d7f6d594bb9895599abea9d1a0539f13888781ae8"
|
||||
checksum = "d5c834ca54c5a20588b358f34d1533b4b498ddb5fd979cec6b22d0e8867a2449"
|
||||
dependencies = [
|
||||
"bon",
|
||||
"caseless",
|
||||
|
|
@ -1836,9 +1837,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
|||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72"
|
||||
checksum = "a25169bd5913a4b437588a7e3d127cd6e90127b60e0ffbd834a38f1599e016b8"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
|
|
@ -2536,7 +2537,6 @@ dependencies = [
|
|||
"serde",
|
||||
"sqlx",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tower",
|
||||
|
|
@ -2548,9 +2548,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.11"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
|
||||
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
|
@ -3268,6 +3268,9 @@ name = "strum"
|
|||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
|
|
@ -4222,9 +4225,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.8"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b"
|
||||
checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -31,13 +31,12 @@ sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio", "chrono"] }
|
|||
async-compression = { version = "0.4", features = ["tokio", "gzip"] }
|
||||
|
||||
askama = "0.14"
|
||||
comrak = "0.38"
|
||||
comrak = "0.39"
|
||||
|
||||
argon2 = { version = "0.5", features = ["default", "std"] }
|
||||
rand_core = { version = "0.9", features = ["std"] }
|
||||
rand = "0.9"
|
||||
strum = "0.27"
|
||||
strum_macros = "0.27"
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
async-trait = "0.1"
|
||||
|
||||
lettre = { version = "0.11", default-features = false, features = [
|
||||
|
|
|
|||
|
|
@ -233,6 +233,7 @@ pub fn make_service(
|
|||
"/shopping_list/checked",
|
||||
patch(services::ron::shopping_list::set_entry_checked),
|
||||
)
|
||||
.route("/translation", get(services::ron::get_translation))
|
||||
.fallback(services::ron::not_found);
|
||||
|
||||
let fragments_routes = Router::new().route(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use chrono::{Duration, prelude::*};
|
||||
use rand::distr::{Alphanumeric, SampleString};
|
||||
use sqlx::Sqlite;
|
||||
use strum_macros::Display;
|
||||
use strum::Display;
|
||||
|
||||
use super::{Connection, DBError, Result};
|
||||
use crate::{
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
use axum::{
|
||||
debug_handler,
|
||||
extract::{Extension, State},
|
||||
extract::{Extension, Query, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::{IntoResponse, Result},
|
||||
};
|
||||
use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
|
||||
use common::ron_api;
|
||||
|
||||
use crate::{
|
||||
app::Context, consts, data::db, data::model, ron_extractor::ExtractRon, ron_utils::ron_error,
|
||||
app::Context,
|
||||
consts,
|
||||
data::{db, model},
|
||||
ron_extractor::ExtractRon,
|
||||
ron_utils::{ron_error, ron_response_ok},
|
||||
};
|
||||
|
||||
pub mod calendar;
|
||||
|
|
@ -36,6 +41,16 @@ pub async fn set_lang(
|
|||
Ok((jar, StatusCode::OK))
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn get_translation(
|
||||
Extension(context): Extension<Context>,
|
||||
translation_id: Query<ron_api::Id>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
Ok(ron_response_ok(ron_api::Value {
|
||||
value: context.tr.t_from_id(translation_id.id),
|
||||
}))
|
||||
}
|
||||
|
||||
/*** 404 ***/
|
||||
|
||||
#[debug_handler]
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ pub async fn get(
|
|||
pub async fn set_entry_checked(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(context): Extension<Context>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::Value<bool>>,
|
||||
ExtractRon(ron): ExtractRon<ron_api::KeyValue<bool>>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_shopping_list_entry(&connection, &context.user, ron.id).await?;
|
||||
Ok(ron_response_ok(
|
||||
|
|
|
|||
|
|
@ -367,7 +367,15 @@ pub async fn sign_in_post(
|
|||
.same_site(cookie::SameSite::Strict);
|
||||
Ok((
|
||||
jar.add(cookie),
|
||||
Redirect::to(&format!("/{}/", context.tr.current_lang_code())).into_response(),
|
||||
Redirect::to(&format!(
|
||||
"/{}/?{}={}&{}={}",
|
||||
context.tr.current_lang_code(),
|
||||
common::consts::GET_PARAMETER_USER_MESSAGE,
|
||||
Sentence::SignInSuccess as i64,
|
||||
common::consts::GET_PARAMETER_USER_MESSAGE_LEVEL,
|
||||
common::toast::Level::Success as usize
|
||||
))
|
||||
.into_response(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ use common::utils;
|
|||
use ron::de::from_reader;
|
||||
use serde::Deserialize;
|
||||
use strum::EnumCount;
|
||||
use strum_macros::EnumCount;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::consts;
|
||||
|
||||
#[repr(i64)]
|
||||
#[derive(Debug, Clone, EnumCount, Deserialize)]
|
||||
pub enum Sentence {
|
||||
MainTitle = 0,
|
||||
|
|
@ -34,6 +34,7 @@ pub enum Sentence {
|
|||
SignInMenu,
|
||||
SignInTitle,
|
||||
SignInButton,
|
||||
SignInSuccess,
|
||||
WrongEmailOrPassword,
|
||||
|
||||
// Sign up page.
|
||||
|
|
@ -188,6 +189,11 @@ impl Tr {
|
|||
self.lang.get(sentence)
|
||||
}
|
||||
|
||||
pub fn t_from_id(&self, sentence_id: i64) -> &'static str {
|
||||
self.lang.get_from_id(sentence_id)
|
||||
}
|
||||
|
||||
/// Translate a sentence with parameters.
|
||||
pub fn tp(&self, sentence: Sentence, params: &[Box<dyn ToString + Send>]) -> String {
|
||||
let text = self.lang.get(sentence);
|
||||
let params_as_string: Vec<String> = params.iter().map(|p| p.to_string()).collect();
|
||||
|
|
@ -246,6 +252,7 @@ impl Tr {
|
|||
| "VE" // Venezuela.
|
||||
| "ZW" // Zimbabwe.
|
||||
=> Weekday::Sun,
|
||||
|
||||
"AF" // Afghanistan.
|
||||
| "DZ" // Algeria.
|
||||
| "BH" // Bahrain.
|
||||
|
|
@ -262,6 +269,7 @@ impl Tr {
|
|||
| "AE" // United Arab Emirates.
|
||||
| "YE" // Yemen.
|
||||
=> Weekday::Sat,
|
||||
|
||||
_ => Weekday::Mon,
|
||||
}
|
||||
}
|
||||
|
|
@ -320,14 +328,17 @@ impl Language {
|
|||
T: Borrow<Sentence>,
|
||||
{
|
||||
let sentence_cloned: Sentence = sentence.borrow().clone();
|
||||
self.get_from_id(sentence_cloned as i64)
|
||||
}
|
||||
|
||||
let text: &str = match self.translation.get(sentence_cloned as usize) {
|
||||
pub fn get_from_id(&'static self, sentence_id: i64) -> &'static str {
|
||||
let text: &str = match self.translation.get(sentence_id as usize) {
|
||||
None => UNABLE_TO_FIND_TRANSLATION_MESSAGE,
|
||||
Some(text) => text,
|
||||
};
|
||||
|
||||
if text.is_empty() && self.code != DEFAULT_LANGUAGE_CODE {
|
||||
return get_language_translation(DEFAULT_LANGUAGE_CODE).get(sentence);
|
||||
return get_language_translation(DEFAULT_LANGUAGE_CODE).get_from_id(sentence_id);
|
||||
}
|
||||
text
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@
|
|||
(SignInMenu, "Sign in"),
|
||||
(SignInTitle, "Sign in"),
|
||||
(SignInButton, "Sign in"),
|
||||
(SignInSuccess, "Sign in successful"),
|
||||
(WrongEmailOrPassword, "Wrong email or password"),
|
||||
|
||||
(AccountMustBeValidatedFirst, "This account must be validated first"),
|
||||
(InvalidEmail, "Invalid email"),
|
||||
(PasswordDontMatch, "Passwords don't match"),
|
||||
|
|
@ -175,7 +177,9 @@
|
|||
(SignInMenu, "Se connecter"),
|
||||
(SignInTitle, "Se connecter"),
|
||||
(SignInButton, "Se connecter"),
|
||||
(SignInSuccess, "Connexion réussie"),
|
||||
(WrongEmailOrPassword, "Mot de passe ou email invalide"),
|
||||
|
||||
(AccountMustBeValidatedFirst, "Ce compte doit d'abord être validé"),
|
||||
(InvalidEmail, "Adresse email invalide"),
|
||||
(PasswordDontMatch, "Les mots de passe ne correspondent pas"),
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ edition = "2024"
|
|||
ron = "0.10"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
pub const MIN_PASSWORD_SIZE: usize = 8;
|
||||
pub const COOKIE_DARK_THEME: &str = "dark_theme";
|
||||
|
||||
pub const GET_PARAMETER_USER_MESSAGE: &str = "user_message";
|
||||
pub const GET_PARAMETER_USER_MESSAGE_LEVEL: &str = "user_message_icon";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod consts;
|
||||
pub mod ron_api;
|
||||
pub mod toast;
|
||||
pub mod utils;
|
||||
|
|
|
|||
|
|
@ -19,9 +19,15 @@ pub struct Id {
|
|||
pub id: i64,
|
||||
}
|
||||
|
||||
// A value associated with an id.
|
||||
// A simple value.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct Value<T> {
|
||||
pub value: T,
|
||||
}
|
||||
|
||||
// A value associated with an id.
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct KeyValue<T> {
|
||||
pub id: i64,
|
||||
pub value: T,
|
||||
}
|
||||
|
|
|
|||
10
common/src/toast.rs
Normal file
10
common/src/toast.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
use strum::FromRepr;
|
||||
|
||||
#[derive(FromRepr)]
|
||||
pub enum Level {
|
||||
Success,
|
||||
Error,
|
||||
Info,
|
||||
Warning,
|
||||
Unknown,
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ web-sys = { version = "0.3", features = [
|
|||
"DataTransfer",
|
||||
"DomRect",
|
||||
"KeyboardEvent",
|
||||
"History",
|
||||
"Element",
|
||||
"DomStringMap",
|
||||
"HtmlDocument",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,44 @@ pub fn main() -> Result<(), JsValue> {
|
|||
_ => log!("Path unknown: ", location),
|
||||
}
|
||||
|
||||
// User message handling.
|
||||
let user_message_params = utils::extract_get_parameters(&[
|
||||
common::consts::GET_PARAMETER_USER_MESSAGE,
|
||||
common::consts::GET_PARAMETER_USER_MESSAGE_LEVEL,
|
||||
]);
|
||||
if let Some(mess_id) = user_message_params.iter().find_map(|(k, v)| {
|
||||
if k == common::consts::GET_PARAMETER_USER_MESSAGE {
|
||||
v.parse::<i64>().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
let level_id = user_message_params.iter().find_map(|(k, v)| {
|
||||
if k == common::consts::GET_PARAMETER_USER_MESSAGE_LEVEL {
|
||||
v.parse::<usize>().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// Request the message to display.
|
||||
spawn_local(async move {
|
||||
let translation: ron_api::Value<String> =
|
||||
request::get("translation", ron_api::Id { id: mess_id })
|
||||
.await
|
||||
.unwrap();
|
||||
if let Some(level_id) = level_id {
|
||||
toast::show_message_level(
|
||||
common::toast::Level::from_repr(level_id)
|
||||
.unwrap_or(common::toast::Level::Unknown),
|
||||
&translation.value,
|
||||
);
|
||||
} else {
|
||||
toast::show_message(&translation.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Language handling.
|
||||
let select_language: HtmlSelectElement = by_id("select-website-language");
|
||||
EventListener::new(&select_language.clone(), "input", move |_event| {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ impl ShoppingList {
|
|||
} else {
|
||||
request::patch(
|
||||
"shopping_list/checked",
|
||||
ron_api::Value {
|
||||
ron_api::KeyValue {
|
||||
id: item_id,
|
||||
value: is_checked,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,16 +1,9 @@
|
|||
pub use common::toast::Level;
|
||||
use gloo::{events::EventListener, timers::callback::Timeout};
|
||||
use web_sys::{Element, HtmlElement, HtmlImageElement};
|
||||
|
||||
use crate::utils::{SelectorExt, by_id, selector_and_clone};
|
||||
|
||||
pub enum Level {
|
||||
Success,
|
||||
Error,
|
||||
Info,
|
||||
Warning,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
const TIME_ANIMATION: u32 = 500; // [ms].
|
||||
const TIME_DISPLAYED: u32 = 5_000; // [ms].
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use chrono::Locale;
|
||||
use gloo::utils::document;
|
||||
use gloo::utils::{document, window};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::Element;
|
||||
|
||||
|
|
@ -133,3 +133,45 @@ pub fn get_locale() -> Locale {
|
|||
.replace("-", "_");
|
||||
Locale::from_str(&lang_and_territory).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Extracts and remove some URL parameters from the current URL.
|
||||
pub fn extract_get_parameters(names: &[&str]) -> Vec<(String, String)> {
|
||||
let mut param_values = vec![];
|
||||
let location = window().location();
|
||||
|
||||
if let Ok(search) = location.search() {
|
||||
if !search.is_empty() && search.starts_with('?') {
|
||||
let mut search_chars = search.chars();
|
||||
search_chars.next(); // To remove the first character which is '?'.
|
||||
let mut new_search = String::with_capacity(search.len());
|
||||
for kv in search_chars.as_str().split('&') {
|
||||
match kv.split('=').collect::<Vec<&str>>()[..] {
|
||||
[key, value] if names.contains(&key) => {
|
||||
param_values.push((key.to_string(), value.to_string()));
|
||||
}
|
||||
_ => {
|
||||
if !new_search.is_empty() {
|
||||
new_search.push('&');
|
||||
}
|
||||
new_search.push_str(kv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !param_values.is_empty() {
|
||||
let mut new_url = location.pathname().unwrap();
|
||||
if !new_search.is_empty() {
|
||||
new_url.push('?');
|
||||
new_url.push_str(&new_search);
|
||||
}
|
||||
window()
|
||||
.history()
|
||||
.unwrap()
|
||||
.push_state_with_url(&JsValue::null(), "", Some(&new_url))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
param_values
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue