Set user language to new recipe + cleaning

This commit is contained in:
Greg Burri 2025-01-06 22:52:22 +01:00
parent 8b4b788562
commit 03ebbb74fa
12 changed files with 73 additions and 86 deletions

View 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
});

View file

@ -32,9 +32,6 @@ pub enum DBError {
)]
UnsupportedVersion(u32),
#[error("Unknown language: {0}")]
UnknownLanguage(String),
#[error("Unknown error: {0}")]
Other(String),
}

View 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(

View 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,

View 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)

View 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 })
}

View 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)

View 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)