Cover use case of manually inserted recipes; allow recipe names instead of URLs in CSV
This commit is contained in:
parent
fe363c0e26
commit
7495682e41
|
@ -128,7 +128,7 @@
|
|||
"2022-05-07","sabato",,"https://ricette.giallozafferano.it/Moussaka.html"
|
||||
"2022-05-08","domenica",,
|
||||
"2022-05-09","lunedì",,
|
||||
"2022-05-10","martedì",,
|
||||
"2022-05-10","martedì",,"https://ricette.giallozafferano.it/Torta-salata-di-melanzane.html"
|
||||
"2022-05-11","mercoledì",,"https://ricette.giallozafferano.it/Mezze-maniche-al-tonno.html"
|
||||
"2022-05-12","giovedì",,
|
||||
"2022-05-13","venerdì",,"https://ricette.giallozafferano.it/Spiedini-di-pollo.html"
|
||||
|
@ -193,7 +193,7 @@
|
|||
"2022-07-11","lunedì",,"https://ricette.giallozafferano.it/Insalata-con-uova-strapazzate.html"
|
||||
"2022-07-12","martedì",,
|
||||
"2022-07-13","mercoledì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
|
||||
"2022-07-14","giovedì",,
|
||||
"2022-07-14","giovedì",,"Tomatenpita"
|
||||
"2022-07-15","venerdì",,"https://ricette.giallozafferano.it/Salmorejo.html"
|
||||
"2022-07-16","sabato",,
|
||||
"2022-07-17","domenica",,"https://ricette.giallozafferano.it/Insalata-di-quinoa-alla-greca.html"
|
||||
|
@ -213,17 +213,17 @@
|
|||
"2022-07-31","domenica",,
|
||||
"2022-08-01","lunedì",,"https://ricette.giallozafferano.it/Riso-freddo-con-tonno-zucchine-e-limone.html"
|
||||
"2022-08-02","martedì",,"https://ricette.giallozafferano.it/Insalata-Shirazi.html"
|
||||
"2022-08-03","mercoledì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
|
||||
"2022-08-03","mercoledì",,"https://ricette.giallozafferano.it/Insalata-di-pasta-Mediterranea.html"
|
||||
"2022-08-04","giovedì",,"https://ricette.giallozafferano.it/Petto-di-pollo-ai-peperoni.html"
|
||||
"2022-08-05","venerdì",,"https://ricette.giallozafferano.it/Pasta-con-pomodorini-e-stracchino.html"
|
||||
"2022-08-06","sabato","https://ricette.giallozafferano.it/Spaghetti-di-riso-con-carne-e-verdure.html",
|
||||
"2022-08-07","domenica",,
|
||||
"2022-08-08","lunedì",,
|
||||
"2022-08-09","martedì",,"https://ricette.giallozafferano.it/Insalata-di-pasta-Mediterranea.html"
|
||||
"2022-08-05","venerdì",,
|
||||
"2022-08-06","sabato","https://ricette.giallozafferano.it/Spaghetti-di-riso-con-carne-e-verdure.html","https://ricette.giallozafferano.it/Pasta-con-pomodorini-e-stracchino.html"
|
||||
"2022-08-07","domenica",,"https://ricette.giallozafferano.it/Insalata-di-riso-vegetariana.html"
|
||||
"2022-08-08","lunedì",,"https://ricette.giallozafferano.it/Torta-salata-di-melanzane.html"
|
||||
"2022-08-09","martedì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
|
||||
"2022-08-10","mercoledì",,"https://ricette.giallozafferano.it/Insalata-con-uova-strapazzate.html"
|
||||
"2022-08-11","giovedì",,"https://ricette.giallozafferano.it/Verdure-gratinate-al-forno.html"
|
||||
"2022-08-12","venerdì",,"https://ricette.giallozafferano.it/Garganelli-con-pesto-di-zucchine-e-gamberetti.html"
|
||||
"2022-08-13","sabato","https://ricette.giallozafferano.it/Insalata-di-bulgur-vegana.html",
|
||||
"2022-08-13","sabato","https://ricette.giallozafferano.it/Insalata-di-bulgur-vegana.html","Tomatenpita"
|
||||
"2022-08-14","domenica",,
|
||||
"2022-08-15","lunedì",,"https://ricette.giallozafferano.it/Pasta-con-le-melanzane.html"
|
||||
"2022-08-16","martedì",,"https://ricette.giallozafferano.it/Tempeh-alle-verdure.html"
|
||||
|
@ -254,7 +254,7 @@
|
|||
"2022-09-10","sabato","https://ricette.giallozafferano.it/Maccheroncini-al-fume.html",
|
||||
"2022-09-11","domenica","https://ricette.giallozafferano.it/Polpo-alla-Luciana.html",
|
||||
"2022-09-12","lunedì",,"https://ricette.giallozafferano.it/Pennette-con-speck-e-zucchine.html"
|
||||
"2022-09-13","martedì",,
|
||||
"2022-09-13","martedì",,"https://ricette.giallozafferano.it/Torta-salata-di-melanzane.html"
|
||||
"2022-09-14","mercoledì",,"https://ricette.giallozafferano.it/Scaloppine-ai-funghi.html"
|
||||
"2022-09-15","giovedì",,
|
||||
"2022-09-16","venerdì",,"https://ricette.giallozafferano.it/Cordon-bleu-di-melanzane.html"
|
||||
|
|
|
|
@ -9,10 +9,10 @@ use {
|
|||
|
||||
pub struct ApiClient {
|
||||
rest: reqwest::Client,
|
||||
pub(crate) rest_semaphore: Arc<Semaphore>, // TODO: wrap in dereferentiable struct
|
||||
rest_semaphore: Arc<Semaphore>,
|
||||
base_url: Url,
|
||||
caldav_base_url: Url,
|
||||
username: String,
|
||||
webdav_base_url: Url,
|
||||
}
|
||||
|
||||
impl ApiClient {
|
||||
|
@ -30,6 +30,8 @@ impl ApiClient {
|
|||
)
|
||||
})?;
|
||||
|
||||
let username = server.login_name.clone();
|
||||
|
||||
use reqwest::header;
|
||||
let mut default_headers = header::HeaderMap::new();
|
||||
let mut auth_header = b"Basic ".to_vec();
|
||||
|
@ -53,12 +55,14 @@ impl ApiClient {
|
|||
.send(),
|
||||
)?
|
||||
.url()
|
||||
.clone();
|
||||
.join(&format!("calendars/{}/", &username))?;
|
||||
|
||||
let webdav_base_url = base_url.join(&format!("remote.php/dav/files/{}/", username))?;
|
||||
|
||||
Ok(ApiClient {
|
||||
base_url,
|
||||
caldav_base_url,
|
||||
username: server.login_name.clone(),
|
||||
webdav_base_url,
|
||||
rest: rest_client,
|
||||
rest_semaphore: Arc::new(Semaphore::new(5)),
|
||||
})
|
||||
|
@ -94,10 +98,6 @@ impl ApiClient {
|
|||
&self.caldav_base_url
|
||||
}
|
||||
|
||||
pub fn username(&self) -> &str {
|
||||
&self.username
|
||||
}
|
||||
|
||||
pub async fn get_events<Tz>(
|
||||
&self,
|
||||
calendar_name: &str,
|
||||
|
@ -113,10 +113,8 @@ impl ApiClient {
|
|||
let response = client
|
||||
.request(
|
||||
report_method.clone(),
|
||||
// TODO extract into helper method
|
||||
self.caldav_base_url.join(&format!(
|
||||
"calendars/{}/{}",
|
||||
self.username(),
|
||||
"{}/",
|
||||
calendar_name.to_lowercase().replace(" ", "-")
|
||||
))?,
|
||||
)
|
||||
|
@ -195,4 +193,8 @@ impl ApiClient {
|
|||
|
||||
Ok(events.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
pub fn webdav_base_url(&self) -> &Url {
|
||||
&self.webdav_base_url
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use {
|
|||
chrono::{Duration, Local},
|
||||
icalendar::Component,
|
||||
regex::Regex,
|
||||
reqwest::{Method, StatusCode},
|
||||
reqwest::{Method, StatusCode, Url},
|
||||
std::collections::HashSet,
|
||||
std::ops::Range,
|
||||
};
|
||||
|
@ -109,43 +109,16 @@ fn prepare_grocery_list(ingredients: &Vec<Ingredient>) -> Result<String> {
|
|||
}
|
||||
|
||||
async fn save_grocery_list(api_client: &ApiClient, filename: &str, contents: &str) -> Result<()> {
|
||||
let dav_base_url = api_client
|
||||
.base_url()
|
||||
.join(&format!("remote.php/dav/files/{}/", api_client.username()))?;
|
||||
|
||||
let filename_components = filename.split('/').collect::<Vec<_>>();
|
||||
filename_components
|
||||
.iter()
|
||||
.take(filename_components.len() - 1)
|
||||
.fold(Ok(dav_base_url.clone()), |url, dir| {
|
||||
.fold(Ok(api_client.webdav_base_url().clone()), |url, dir| {
|
||||
url.map(|u| u.join(&format!("{dir}/")).unwrap())
|
||||
.and_then(|url| {
|
||||
futures::executor::block_on(async {
|
||||
let response = api_client
|
||||
.rest(|client| async {
|
||||
let r = client
|
||||
.request(Method::from_bytes(b"MKCOL").unwrap(), url.clone())
|
||||
.send()
|
||||
.await;
|
||||
Ok(r?)
|
||||
})
|
||||
.await;
|
||||
|
||||
match response.map(|r| r.status()) {
|
||||
Ok(StatusCode::OK)
|
||||
| Ok(StatusCode::METHOD_NOT_ALLOWED /* already exists */) => Ok(url),
|
||||
Ok(status) => Err(anyhow!(
|
||||
"Could not create WebDAV collection {}, server responded with {}",
|
||||
&url,
|
||||
status
|
||||
)),
|
||||
Err(e) => Err(anyhow!(e)),
|
||||
}
|
||||
})
|
||||
})
|
||||
.and_then(|url| ensure_collection_exist(api_client, url))
|
||||
})?;
|
||||
|
||||
let file_url = dav_base_url.join(filename).unwrap();
|
||||
let file_url = api_client.webdav_base_url().join(filename).unwrap();
|
||||
log::info!("Saving grocery list to {}", &file_url);
|
||||
let response = api_client
|
||||
.rest(|client| async {
|
||||
|
@ -168,3 +141,27 @@ async fn save_grocery_list(api_client: &ApiClient, filename: &str, contents: &st
|
|||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_collection_exist(api_client: &ApiClient, url: Url) -> Result<Url> {
|
||||
futures::executor::block_on(async {
|
||||
let response = api_client
|
||||
.rest(|client| async {
|
||||
let r = client
|
||||
.request(Method::from_bytes(b"MKCOL").unwrap(), url.clone())
|
||||
.send()
|
||||
.await;
|
||||
Ok(r?)
|
||||
})
|
||||
.await;
|
||||
|
||||
match response.map(|r| r.status()) {
|
||||
Ok(StatusCode::OK) | Ok(StatusCode::METHOD_NOT_ALLOWED /* already exists */) => Ok(url),
|
||||
Ok(status) => Err(anyhow!(
|
||||
"Could not create WebDAV collection {}, server responded with {}",
|
||||
&url,
|
||||
status
|
||||
)),
|
||||
Err(e) => Err(anyhow!(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,10 +10,8 @@ pub async fn with(api_client: &ApiClient, calendar_name: &str) -> Result<()> {
|
|||
let response = client
|
||||
.request(
|
||||
report_method.clone(),
|
||||
// TODO extract into helper method
|
||||
api_client.caldav_base_url().join(&format!(
|
||||
"calendars/{}/{}/",
|
||||
api_client.username(),
|
||||
"{}/",
|
||||
calendar_name.to_lowercase().replace(" ", "-")
|
||||
))?,
|
||||
)
|
||||
|
|
|
@ -11,7 +11,7 @@ use {
|
|||
chrono::naive::NaiveDate,
|
||||
futures::future::try_join_all,
|
||||
icalendar::Event,
|
||||
reqwest::StatusCode,
|
||||
reqwest::{StatusCode, Url},
|
||||
std::collections::{HashMap, HashSet},
|
||||
std::fmt::Write,
|
||||
std::iter::Iterator,
|
||||
|
@ -61,17 +61,17 @@ pub async fn with(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result<HashSet<String>>
|
||||
fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result<HashSet<Url>>
|
||||
where
|
||||
RecordsIter: Iterator<Item = &'a CsvRecord>,
|
||||
{
|
||||
Ok(
|
||||
records.fold(std::collections::HashSet::new(), |mut set, r| {
|
||||
if !r.lunch.is_empty() {
|
||||
set.insert(r.lunch.clone());
|
||||
if let Ok(lunch) = Url::try_from(r.lunch.as_str()) {
|
||||
set.insert(lunch);
|
||||
}
|
||||
if !r.dinner.is_empty() {
|
||||
set.insert(r.dinner.clone());
|
||||
if let Ok(dinner) = Url::try_from(r.dinner.as_str()) {
|
||||
set.insert(dinner);
|
||||
}
|
||||
set
|
||||
}),
|
||||
|
@ -93,35 +93,20 @@ async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<re
|
|||
.await?;
|
||||
|
||||
let recipes = metadata.iter().map(|rm| async {
|
||||
// TODO: wrap this, heavily refactor this mess
|
||||
let _ = api_client.rest_semaphore.acquire().await.unwrap();
|
||||
let mut retries = 0;
|
||||
let response = loop {
|
||||
let result = api_client
|
||||
.rest(|client| async {
|
||||
let response = client
|
||||
.get(
|
||||
api_client
|
||||
.base_url()
|
||||
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id))
|
||||
.unwrap(),
|
||||
)
|
||||
.send()
|
||||
.await;
|
||||
Ok(response?)
|
||||
})
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(response) => break response,
|
||||
Err(_) if retries < 10 => {
|
||||
retries += 1;
|
||||
std::thread::sleep(retries * std::time::Duration::from_millis(500));
|
||||
continue;
|
||||
}
|
||||
_ => bail!("Cannot fetch recipe {} with id {}", rm.name, rm.id),
|
||||
}
|
||||
};
|
||||
let response = api_client
|
||||
.rest(|client| async {
|
||||
let r = client
|
||||
.get(
|
||||
api_client
|
||||
.base_url()
|
||||
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id))
|
||||
.unwrap(),
|
||||
)
|
||||
.send()
|
||||
.await;
|
||||
Ok(r?)
|
||||
})
|
||||
.await?;
|
||||
|
||||
response
|
||||
.json::<recipe::Recipe>()
|
||||
|
@ -131,9 +116,17 @@ async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<re
|
|||
});
|
||||
|
||||
let recipes = try_join_all(recipes).await?;
|
||||
Ok(HashMap::from_iter(
|
||||
recipes.into_iter().map(|r| (r.url.clone(), r)),
|
||||
))
|
||||
|
||||
Ok(HashMap::from_iter(recipes.into_iter().map(|r| {
|
||||
// Fallback to recipe name as ID if URL is empty
|
||||
let id = match &r.url {
|
||||
Some(u) if !u.is_empty() => u,
|
||||
_ => &r.name,
|
||||
}
|
||||
.clone();
|
||||
|
||||
(id, r)
|
||||
})))
|
||||
}
|
||||
|
||||
async fn publish_events<'a, SchedulingsIter>(
|
||||
|
@ -146,8 +139,7 @@ where
|
|||
SchedulingsIter: Iterator<Item = Scheduling>,
|
||||
{
|
||||
let calendar_url = api_client.caldav_base_url().join(&format!(
|
||||
"calendars/{}/{}/",
|
||||
&api_client.username(),
|
||||
"{}/",
|
||||
calendar.to_lowercase().as_str().replace(" ", "-")
|
||||
))?;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ pub struct Metadata {
|
|||
#[serde(rename = "recipe_id")]
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub keywords: String,
|
||||
pub keywords: Option<String>,
|
||||
pub date_created: DateTime,
|
||||
pub date_modified: DateTime,
|
||||
}
|
||||
|
@ -24,8 +24,8 @@ pub struct Recipe {
|
|||
pub id: isize,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub url: String,
|
||||
pub keywords: String,
|
||||
pub url: Option<String>,
|
||||
pub keywords: Option<String>,
|
||||
|
||||
#[serde(rename = "dateCreated")]
|
||||
pub created: DateTime,
|
||||
|
|
|
@ -49,16 +49,20 @@ impl From<Scheduling> for Event {
|
|||
use icalendar::Component;
|
||||
let start_time = ev.ends_at - ev.recipe.total_time();
|
||||
|
||||
let cal_event = Event::new()
|
||||
let mut cal_event = Event::new();
|
||||
|
||||
cal_event
|
||||
.uid(&ev.uid)
|
||||
.summary(&ev.recipe.name)
|
||||
.description(&format!("cookbook@{}", ev.recipe.id))
|
||||
.location(&ev.recipe.url)
|
||||
.timestamp(Utc::now())
|
||||
.starts(start_time)
|
||||
.ends(ev.ends_at)
|
||||
.done();
|
||||
.ends(ev.ends_at);
|
||||
|
||||
cal_event
|
||||
if let Some(ref location) = ev.recipe.url.clone() {
|
||||
cal_event.location(&location);
|
||||
}
|
||||
|
||||
cal_event.done()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue