Update to NC Cookbok's API 0.1.0

This commit is contained in:
Matteo Settenvini 2022-09-20 23:03:00 +02:00
parent 8930a2a5a1
commit 29b7ec7877
Signed by: matteo
GPG Key ID: 8576CC1AD97D42DF
5 changed files with 65 additions and 35 deletions

View File

@ -76,7 +76,7 @@ where
{ {
let ingredients = recipe_ids.into_iter().map(|id: usize| async move { let ingredients = recipe_ids.into_iter().map(|id: usize| async move {
// TODO code duplicated with schedule_csv::get_all_recipes // TODO code duplicated with schedule_csv::get_all_recipes
let recipe_url = format!("apps/cookbook/api/recipes/{id}"); let recipe_url = format!("apps/cookbook/api/v1/recipes/{id}");
let response = api_client let response = api_client
.rest(|client| async { .rest(|client| async {
Ok(client Ok(client

View File

@ -12,7 +12,7 @@ where
let response = api_client let response = api_client
.rest(|client| async { .rest(|client| async {
let r = client let r = client
.post(api_client.base_url().join("apps/cookbook/import")?) .post(api_client.base_url().join("apps/cookbook/api/v1/import")?)
.json(&serde_json::json!({ .json(&serde_json::json!({
"url": url.as_ref(), "url": url.as_ref(),
})) }))

View File

@ -7,7 +7,7 @@ pub async fn with(api_client: &ApiClient) -> Result<()> {
let recipes = api_client let recipes = api_client
.rest(|client| async { .rest(|client| async {
let response = client let response = client
.get(api_client.base_url().join("apps/cookbook/api/recipes")?) .get(api_client.base_url().join("apps/cookbook/api/v1/recipes")?)
.send() .send()
.await; .await;
Ok(response?) Ok(response?)

View File

@ -80,22 +80,22 @@ where
async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<recipe::Recipe>>> { async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<recipe::Recipe>>> {
log::info!("Getting list of all recipes"); log::info!("Getting list of all recipes");
let metadata = api_client let response = api_client
.rest(|client| async { .rest(|client| async {
let response = client let response = client
.get(api_client.base_url().join("apps/cookbook/api/recipes")?) .get(api_client.base_url().join("apps/cookbook/api/v1/recipes")?)
.send() .send()
.await; .await;
Ok(response?) Ok(response?)
}) })
.await?
.json::<Vec<recipe::Metadata>>()
.await?; .await?;
let metadata = response.json::<Vec<recipe::Metadata>>().await?;
let recipes = metadata.iter().map(|rm| async { let recipes = metadata.iter().map(|rm| async {
let recipe_url = api_client let recipe_url = api_client
.base_url() .base_url()
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id)) .join(&format!("apps/cookbook/api/v1/recipes/{id}", id = rm.id))
.unwrap(); .unwrap();
let response = api_client let response = api_client
.rest(|client| async { .rest(|client| async {

View File

@ -10,10 +10,14 @@ use {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Metadata { pub struct Metadata {
#[serde(rename = "recipe_id")] #[serde(rename = "recipe_id")]
pub id: u32, pub id: u64,
pub name: String, pub name: String,
pub keywords: Option<String>, pub keywords: Option<String>,
#[serde(with = "naive_date_format")]
pub date_created: DateTime, pub date_created: DateTime,
#[serde(with = "naive_date_format")]
pub date_modified: DateTime, pub date_modified: DateTime,
} }
@ -21,7 +25,7 @@ pub struct Metadata {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Recipe { pub struct Recipe {
pub id: isize, pub id: u64,
pub name: String, pub name: String,
pub description: String, pub description: String,
pub url: Option<String>, pub url: Option<String>,
@ -35,15 +39,15 @@ pub struct Recipe {
pub image_url: String, pub image_url: String,
#[serde(deserialize_with = "deserialize_duration")] #[serde(with = "duration")]
pub prep_time: Duration, pub prep_time: Duration,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_maybe_duration")] #[serde(with = "maybe_duration")]
pub cook_time: Option<Duration>, pub cook_time: Option<Duration>,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_maybe_duration")] #[serde(with = "maybe_duration")]
pub total_time: Option<Duration>, pub total_time: Option<Duration>,
pub image: Option<String>, pub image: Option<String>,
@ -101,36 +105,62 @@ pub struct Nutrition {
type DateTime = chrono::DateTime<chrono::Utc>; type DateTime = chrono::DateTime<chrono::Utc>;
fn deserialize_maybe_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error> mod naive_date_format {
use chrono::{DateTime, TimeZone, Utc};
use serde::{self, Deserialize, Deserializer};
const FORMAT: &'static str = "%Y-%m-%d %H:%M:%S";
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
Ok(Some(deserialize_duration(deserializer)?)) let s = String::deserialize(deserializer)?;
Utc.datetime_from_str(&s, FORMAT)
.map_err(serde::de::Error::custom)
}
} }
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error> mod duration {
use {
super::Duration,
serde::{self, de::Error, Deserialize, Deserializer},
};
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
deserializer.deserialize_str(DurationVisitor) let value = String::deserialize(deserializer)?;
parse_duration::<D>(&value)
} }
struct DurationVisitor; pub fn parse_duration<'de, D>(value: &str) -> Result<Duration, D::Error>
impl<'de> serde::de::Visitor<'de> for DurationVisitor {
type Value = Duration;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a duration in ISO 8601 format")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where where
E: serde::de::Error, D: Deserializer<'de>,
{ {
speedate::Duration::parse_str(value) speedate::Duration::parse_str(&value)
.map(|dt| Duration::seconds(dt.signed_total_seconds())) .map(|dt| Duration::seconds(dt.signed_total_seconds()))
.map_err(|e| E::custom(e.to_string())) .map_err(|e| D::Error::custom(e.to_string()))
}
}
mod maybe_duration {
use {
super::Duration,
serde::{self, Deserialize, Deserializer},
};
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
where
D: Deserializer<'de>,
{
let s: Option<String> = Option::deserialize(deserializer)?;
Ok(if let Some(s) = s {
Some(super::duration::parse_duration::<D>(&s)?)
} else {
None
})
} }
} }