Add a API entry point to get all tags

This commit is contained in:
Greg Burri 2025-05-20 15:32:54 +02:00
parent 6e017e41a3
commit c24b0caeaf
7 changed files with 55 additions and 63 deletions

42
Cargo.lock generated
View file

@ -2890,9 +2890,9 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e"
checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
dependencies = [
"sqlx-core",
"sqlx-macros",
@ -2903,9 +2903,9 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3"
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
dependencies = [
"base64",
"bytes",
@ -2938,9 +2938,9 @@ dependencies = [
[[package]]
name = "sqlx-macros"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce"
checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
dependencies = [
"proc-macro2",
"quote",
@ -2951,9 +2951,9 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7"
checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
dependencies = [
"dotenvy",
"either",
@ -2970,16 +2970,15 @@ dependencies = [
"sqlx-postgres",
"sqlx-sqlite",
"syn",
"tempfile",
"tokio",
"url",
]
[[package]]
name = "sqlx-mysql"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7"
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
"base64",
@ -3020,9 +3019,9 @@ dependencies = [
[[package]]
name = "sqlx-postgres"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6"
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
"base64",
@ -3058,9 +3057,9 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc"
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
dependencies = [
"atoi",
"chrono",
@ -3248,19 +3247,6 @@ dependencies = [
"yaml-rust",
]
[[package]]
name = "tempfile"
version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [
"fastrand",
"getrandom 0.3.3",
"once_cell",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "tendril"
version = "0.4.3"

View file

@ -155,8 +155,7 @@ pub fn make_service(
.post(services::ron::recipe::add_tags)
.delete(services::ron::recipe::rm_tags),
)
// TODO
// .route("/recipe/tags".get(services::ron::recipe::get_all_tags))
.route("/recipe/tags", get(services::ron::recipe::get_all_tags))
.route(
"/recipe/{id}/difficulty",
patch(services::ron::recipe::set_difficulty),

View file

@ -1,3 +1,5 @@
use std::u32;
use chrono::prelude::*;
use common::ron_api::Difficulty;
use itertools::Itertools;
@ -380,29 +382,20 @@ WHERE [Recipe].[user_id] = $1
.map_err(DBError::from)
}
pub async fn get_all_tags(&self) -> Result<Vec<String>> {
pub async fn get_all_tags(&self, lang: &str, max_number: Option<u32>) -> Result<Vec<String>> {
sqlx::query_scalar(
r#"
SELECT [name] FROM [Tag]
ORDER BY [name]
"#,
)
.fetch_all(&self.pool)
.await
.map_err(DBError::from)
}
pub async fn get_all_tags_by_lang(&self, lang: &str) -> Result<Vec<String>> {
sqlx::query_scalar(
r#"
SELECT DISTINCT [name] FROM [Tag]
INNER JOIN [RecipeTag] ON [RecipeTag].[tag_id] = [Tag].[id]
INNER JOIN [Recipe] ON [Recipe].[id] = [RecipeTag].[recipe_id]
SELECT [name], COUNT([name]) as [nb_used] FROM [Tag]
INNER JOIN [RecipeTag] ON [RecipeTag].[tag_id] = [Tag].[id]
INNER JOIN [Recipe] ON [Recipe].[id] = [RecipeTag].[recipe_id]
WHERE [Recipe].[lang] = $1
ORDER BY [name]
GROUP BY [Tag].[name]
ORDER BY [nb_used] DESC, [name]
LIMIT $2
"#,
)
.bind(lang)
.bind(max_number.unwrap_or(u32::MAX))
.fetch_all(&self.pool)
.await
.map_err(DBError::from)
@ -1078,9 +1071,15 @@ VALUES
assert_eq!(connection.get_recipes_tags(recipe_id_1).await?, tags_1);
assert_eq!(connection.get_recipes_tags(recipe_id_2).await?, tags_2);
assert_eq!(connection.get_all_tags().await?, ["abc", "def", "xyz"]);
assert_eq!(
connection.get_all_tags("en", None).await?,
["abc", "xyz", "def"]
);
connection.rm_recipe_tags(recipe_id_2, &["abc"]).await?;
assert_eq!(connection.get_all_tags().await?, ["abc", "def", "xyz"]);
assert_eq!(
connection.get_all_tags("en", None).await?,
["xyz", "abc", "def"] // Most used tags come first.
);
assert_eq!(
connection.get_recipes_tags(recipe_id_1).await?,
@ -1098,13 +1097,12 @@ VALUES
connection.get_recipes_tags(recipe_id_2).await?,
["def", "xyz"]
);
assert_eq!(connection.get_all_tags().await?, ["def", "xyz"]);
assert_eq!(connection.get_all_tags_by_lang("en").await?, ["def", "xyz"]);
assert_eq!(connection.get_all_tags("en", None).await?, ["xyz", "def"]);
connection.rm_recipe_tags(recipe_id_1, &tags_1).await?;
connection.rm_recipe_tags(recipe_id_2, &tags_2).await?;
assert!(connection.get_all_tags().await?.is_empty());
assert!(connection.get_all_tags("en", None).await?.is_empty());
Ok(())
}

View file

@ -1,11 +1,10 @@
use axum::{
debug_handler,
extract::{Extension, Path, Query, State},
extract::{Extension, Path, State},
http::{HeaderMap, StatusCode},
response::{IntoResponse, Result},
};
use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
use common::ron_api;
use crate::{
app::Context,

View file

@ -88,6 +88,19 @@ pub async fn get_tags(
))
}
#[debug_handler]
pub async fn get_all_tags(
State(connection): State<db::Connection>,
Extension(context): Extension<Context>,
nb_max_tags: Query<Option<u32>>,
lang: Query<Option<String>>,
) -> Result<impl IntoResponse> {
let lang = lang.0.unwrap_or(context.tr.current_lang_code().to_string());
Ok(ron_response_ok(
connection.get_all_tags(&lang, nb_max_tags.0).await?,
))
}
#[debug_handler]
pub async fn add_tags(
State(connection): State<db::Connection>,

View file

@ -1,6 +1,7 @@
use chrono::NaiveDate;
use ron::ser::{PrettyConfig, to_string_pretty};
use serde::{Deserialize, Serialize};
use strum::FromRepr;
/*** Generic types ***/
@ -20,7 +21,7 @@ pub struct DateRange {
/*** Recipe ***/
#[repr(u32)]
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug)]
#[derive(Serialize, Deserialize, FromRepr, Clone, Copy, PartialEq, Debug)]
pub enum Difficulty {
Unknown = 0,
Easy = 1,
@ -29,14 +30,9 @@ pub enum Difficulty {
}
impl TryFrom<u32> for Difficulty {
type Error = &'static str;
type Error = String;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
1 => Self::Easy,
2 => Self::Medium,
3 => Self::Hard,
_ => Self::Unknown,
})
Difficulty::from_repr(value).ok_or(format!("Unable to convert difficulty from {}", value))
}
}

View file

@ -134,7 +134,8 @@ pub fn setup_page(recipe_id: i64) {
if difficulty.value() != current_difficulty {
current_difficulty = difficulty.value();
let difficulty =
ron_api::Difficulty::try_from(difficulty.value().parse::<u32>().unwrap());
ron_api::Difficulty::from_repr(difficulty.value().parse::<u32>().unwrap())
.unwrap_or(ron_api::Difficulty::Unknown);
spawn_local(async move {
let _ = request::patch::<(), _>(
&format!("recipe/{recipe_id}/difficulty"),