-use std::{sync::LazyLock, time::Duration};
+use std::time::Duration;
pub const FILE_CONF: &str = "conf.ron";
pub const TRANSLATION_FILE: &str = "translation.ron";
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
-});
)]
UnsupportedVersion(u32),
- #[error("Unknown language: {0}")]
- UnknownLanguage(String),
-
#[error("Unknown error: {0}")]
Other(String),
}
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)
{
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())
}
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)
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(
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,
middleware::Next,
response::{IntoResponse, Response, Result},
};
-// use tracing::{event, Level};
use crate::{
data::{db, model},
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)
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)
},
current_id: current_recipe.current_recipe_id,
};
- Ok(RecipesListFragmentTemplate { user, tr, recipes })
+ Ok(RecipesListFragmentTemplate { tr, recipes })
}
// use tracing::{event, Level};
use crate::{
- consts,
data::{db, model},
html_templates::*,
translation::{self, Sentence},
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 {
}
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)
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)
<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>
use gloo::{
- console::log,
events::EventListener,
net::http::Request,
utils::{document, window},
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;
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();
let _ = request::put::<(), _>("set_lang", body).await;
let _ = window().location().reload();
});
-
- // log!(lang);
})
.forget();
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,