Set user language to new recipe + cleaning
authorGreg Burri <greg.burri@gmail.com>
Mon, 6 Jan 2025 21:52:22 +0000 (22:52 +0100)
committerGreg Burri <greg.burri@gmail.com>
Mon, 6 Jan 2025 21:52:22 +0000 (22:52 +0100)
12 files changed:
backend/src/consts.rs
backend/src/data/db.rs
backend/src/data/db/recipe.rs
backend/src/html_templates.rs
backend/src/services.rs
backend/src/services/fragments.rs
backend/src/services/recipe.rs
backend/src/services/ron.rs
backend/templates/recipe_edit.html
frontend/src/handles.rs
frontend/src/lib.rs
frontend/src/on_click.rs

index 222499a..bbe1cdd 100644 (file)
@@ -1,4 +1,4 @@
-use std::{sync::LazyLock, time::Duration};
+use std::time::Duration;
 
 pub const FILE_CONF: &str = "conf.ron";
 pub const TRANSLATION_FILE: &str = "translation.ron";
@@ -23,10 +23,3 @@ pub const SEND_EMAIL_TIMEOUT: Duration = Duration::from_secs(60);
 pub const REVERSE_PROXY_IP_HTTP_FIELD: &str = "x-real-ip"; // Set by the reverse proxy (Nginx).
 
 pub const MAX_DB_CONNECTION: u32 = 1; // To avoid database lock.
-
-// TODO: remove, should be replaced by the translation module.
-pub static LANGUAGES: LazyLock<[(&str, &str); 2]> = LazyLock::new(|| {
-    let mut langs = [("Français", "fr"), ("English", "en")];
-    langs.sort();
-    langs
-});
index ab30bb8..bf58047 100644 (file)
@@ -32,9 +32,6 @@ pub enum DBError {
     )]
     UnsupportedVersion(u32),
 
-    #[error("Unknown language: {0}")]
-    UnknownLanguage(String),
-
     #[error("Unknown error: {0}")]
     Other(String),
 }
index 28f714e..48a94f9 100644 (file)
@@ -1,18 +1,38 @@
 use super::{Connection, DBError, Result};
-use crate::{consts, data::model};
+use crate::data::model;
 
 use common::ron_api::Difficulty;
 
 impl Connection {
-    pub async fn get_all_published_recipe_titles(&self) -> Result<Vec<(i64, String)>> {
-        sqlx::query_as(
-            r#"
-SELECT [id], [title]
-FROM [Recipe]
-WHERE [is_published] = true
-ORDER BY [title]
-            "#,
-        )
+    /// Returns all the recipe titles where recipe is written in the given language.
+    /// If a user_id is given, the language constraint is ignored for recipes owned by user_id.
+    pub async fn get_all_published_recipe_titles(
+        &self,
+        lang: &str,
+        user_id: Option<i64>,
+    ) -> Result<Vec<(i64, String)>> {
+        if let Some(user_id) = user_id {
+            sqlx::query_as(
+                r#"
+    SELECT [id], [title]
+    FROM [Recipe]
+    WHERE [is_published] = true AND ([lang] = $1 OR [user_id] = $2)
+    ORDER BY [title]
+                "#,
+            )
+            .bind(lang)
+            .bind(user_id)
+        } else {
+            sqlx::query_as(
+                r#"
+    SELECT [id], [title]
+    FROM [Recipe]
+    WHERE [is_published] = true AND [lang] = $1
+    ORDER BY [title]
+                "#,
+            )
+            .bind(lang)
+        }
         .fetch_all(&self.pool)
         .await
         .map_err(DBError::from)
@@ -147,11 +167,18 @@ WHERE [Recipe].[user_id] = $1
         {
             Some(recipe_id) => Ok(recipe_id),
             None => {
-                let db_result =
-                    sqlx::query("INSERT INTO [Recipe] ([user_id], [title]) VALUES ($1, '')")
-                        .bind(user_id)
-                        .execute(&mut *tx)
-                        .await?;
+                let lang: String = sqlx::query_scalar("SELECT [lang] FROM [User] WHERE [id] = $1")
+                    .bind(user_id)
+                    .fetch_one(&mut *tx)
+                    .await?;
+
+                let db_result = sqlx::query(
+                    "INSERT INTO [Recipe] ([user_id], [lang], [title]) VALUES ($1, $2, '')",
+                )
+                .bind(user_id)
+                .bind(lang)
+                .execute(&mut *tx)
+                .await?;
 
                 tx.commit().await?;
                 Ok(db_result.last_insert_rowid())
@@ -345,9 +372,6 @@ WHERE [id] = $1 AND [id] NOT IN (
     }
 
     pub async fn set_recipe_language(&self, recipe_id: i64, lang: &str) -> Result<()> {
-        if !consts::LANGUAGES.iter().any(|(_, l)| *l == lang) {
-            return Err(DBError::UnknownLanguage(lang.to_string()));
-        }
         sqlx::query("UPDATE [Recipe] SET [lang] = $2 WHERE [id] = $1")
             .bind(recipe_id)
             .bind(lang)
@@ -598,24 +622,6 @@ mod tests {
         Ok(())
     }
 
-    #[tokio::test]
-    async fn set_nonexistent_language() -> Result<()> {
-        let connection = Connection::new_in_memory().await?;
-
-        let user_id = create_a_user(&connection).await?;
-        let recipe_id = connection.create_recipe(user_id).await?;
-
-        match connection.set_recipe_language(recipe_id, "asdf").await {
-            // Nominal case.
-            Err(DBError::UnknownLanguage(message)) => {
-                println!("Ok: {}", message);
-            }
-            other => panic!("Set an nonexistent language must fail: {:?}", other),
-        }
-
-        Ok(())
-    }
-
     async fn create_a_user(connection: &Connection) -> Result<i64> {
         let user_id = 1;
         connection.execute_sql(
index 25b0587..ab8ca0a 100644 (file)
@@ -133,13 +133,11 @@ pub struct RecipeEditTemplate {
     pub recipes: Recipes,
 
     pub recipe: model::Recipe,
-    pub languages: [(&'static str, &'static str); 2],
 }
 
 #[derive(Template)]
 #[template(path = "recipes_list_fragment.html")]
 pub struct RecipesListFragmentTemplate {
-    pub user: Option<model::User>,
     pub tr: Tr,
 
     pub recipes: Recipes,
index 9fcfc1a..1a18e16 100644 (file)
@@ -5,7 +5,6 @@ use axum::{
     middleware::Next,
     response::{IntoResponse, Response, Result},
 };
-// use tracing::{event, Level};
 
 use crate::{
     data::{db, model},
@@ -54,7 +53,9 @@ pub async fn home_page(
     Extension(tr): Extension<translation::Tr>,
 ) -> Result<impl IntoResponse> {
     let recipes = Recipes {
-        published: connection.get_all_published_recipe_titles().await?,
+        published: connection
+            .get_all_published_recipe_titles(tr.current_lang_code(), user.as_ref().map(|u| u.id))
+            .await?,
         unpublished: if let Some(user) = user.as_ref() {
             connection
                 .get_all_unpublished_recipe_titles(user.id)
index 47e9e93..1a50a5f 100644 (file)
@@ -25,7 +25,9 @@ pub async fn recipes_list_fragments(
     Extension(tr): Extension<translation::Tr>,
 ) -> Result<impl IntoResponse> {
     let recipes = Recipes {
-        published: connection.get_all_published_recipe_titles().await?,
+        published: connection
+            .get_all_published_recipe_titles(tr.current_lang_code(), user.as_ref().map(|u| u.id))
+            .await?,
         unpublished: if let Some(user) = user.as_ref() {
             connection
                 .get_all_unpublished_recipe_titles(user.id)
@@ -35,5 +37,5 @@ pub async fn recipes_list_fragments(
         },
         current_id: current_recipe.current_recipe_id,
     };
-    Ok(RecipesListFragmentTemplate { user, tr, recipes })
+    Ok(RecipesListFragmentTemplate { tr, recipes })
 }
index 6d1e0cd..9887b2d 100644 (file)
@@ -6,7 +6,6 @@ use axum::{
 // use tracing::{event, Level};
 
 use crate::{
-    consts,
     data::{db, model},
     html_templates::*,
     translation::{self, Sentence},
@@ -37,21 +36,20 @@ pub async fn edit_recipe(
         if let Some(recipe) = connection.get_recipe(recipe_id, false).await? {
             if recipe.user_id == user.id {
                 let recipes = Recipes {
-                    published: connection.get_all_published_recipe_titles().await?,
+                    published: connection
+                        .get_all_published_recipe_titles(tr.current_lang_code(), Some(user.id))
+                        .await?,
                     unpublished: connection
                         .get_all_unpublished_recipe_titles(user.id)
                         .await?,
                     current_id: Some(recipe_id),
                 };
 
-                dbg!(&recipe);
-
                 Ok(RecipeEditTemplate {
                     user: Some(user),
                     tr,
                     recipes,
                     recipe,
-                    languages: *consts::LANGUAGES,
                 }
                 .into_response())
             } else {
@@ -89,7 +87,12 @@ pub async fn view(
             }
 
             let recipes = Recipes {
-                published: connection.get_all_published_recipe_titles().await?,
+                published: connection
+                    .get_all_published_recipe_titles(
+                        tr.current_lang_code(),
+                        user.as_ref().map(|u| u.id),
+                    )
+                    .await?,
                 unpublished: if let Some(user) = user.as_ref() {
                     connection
                         .get_all_unpublished_recipe_titles(user.id)
index d7c52bc..0d31103 100644 (file)
@@ -249,6 +249,14 @@ pub async fn set_language(
     Extension(user): Extension<Option<model::User>>,
     ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeLanguage>,
 ) -> Result<StatusCode> {
+    if !crate::translation::Tr::available_codes()
+        .iter()
+        .any(|&l| l == ron.lang)
+    {
+        // TODO: log?
+        return Ok(StatusCode::BAD_REQUEST);
+    }
+
     check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
     connection
         .set_recipe_language(ron.recipe_id, &ron.lang)
index be5a91c..7e2b677 100644 (file)
 
     <label for="select-language">{{ tr.t(Sentence::RecipeLanguage) }}</label>
     <select id="select-language">
-    {% for lang in languages %}
-        <option value="{{ lang.1 }}"
-        {%+ if recipe.lang == lang.1 %}
+    {% for lang in Tr::available_languages() %}
+        <option value="{{ lang.0 }}"
+        {%+ if recipe.lang == lang.0 %}
             selected
         {% endif %}
-        >{{ lang.0 }}</option>
+        >{{ lang.1 }}</option>
     {% endfor %}
     </select>
 
index d523042..56b69ac 100644 (file)
@@ -1,5 +1,4 @@
 use gloo::{
-    console::log,
     events::EventListener,
     net::http::Request,
     utils::{document, window},
index 06a8bf7..93c98b9 100644 (file)
@@ -5,11 +5,7 @@ mod request;
 mod toast;
 mod utils;
 
-use gloo::{
-    console::log,
-    events::EventListener,
-    utils::{document, window},
-};
+use gloo::{events::EventListener, utils::window};
 use utils::by_id;
 use wasm_bindgen::prelude::*;
 use wasm_bindgen_futures::spawn_local;
@@ -17,24 +13,10 @@ use web_sys::HtmlSelectElement;
 
 use common::ron_api;
 
-// #[wasm_bindgen]
-// extern "C" {
-//     fn alert(s: &str);
-// }
-
-// #[wasm_bindgen]
-// pub fn greet(name: &str) {
-//     alert(&format!("Hello, {}!", name));
-//     console::log_1(&"Hello bg".into());
-// }
-
 #[wasm_bindgen(start)]
 pub fn main() -> Result<(), JsValue> {
     console_error_panic_hook::set_once();
 
-    // let window = web_sys::window().expect("no global `window` exists");
-    // let document = window.document().expect("should have a document on window");
-
     let location = window().location().pathname()?;
     let path: Vec<&str> = location.split('/').skip(1).collect();
 
@@ -56,8 +38,6 @@ pub fn main() -> Result<(), JsValue> {
             let _ = request::put::<(), _>("set_lang", body).await;
             let _ = window().location().reload();
         });
-
-        // log!(lang);
     })
     .forget();
 
index a634e30..0c2b694 100644 (file)
@@ -1,6 +1,6 @@
 use futures::channel::mpsc;
 use futures::stream::Stream;
-use gloo::{console::log, events::EventListener, net::http::Request, utils::document};
+use gloo::events::EventListener;
 use std::{
     future::Future,
     pin::Pin,