Create (but not publish) events from recipes
This commit is contained in:
parent
f201329441
commit
dce761b0f9
13 changed files with 560 additions and 119 deletions
30
src/commands/import.rs
Normal file
30
src/commands/import.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use {crate::api_client::ApiClient, anyhow::Result, reqwest::StatusCode};
|
||||
|
||||
pub async fn with<UrlsIter>(api_client: &ApiClient, urls: UrlsIter) -> Result<()>
|
||||
where
|
||||
UrlsIter: std::iter::Iterator,
|
||||
UrlsIter::Item: AsRef<str>,
|
||||
{
|
||||
for url in urls {
|
||||
let response = api_client
|
||||
.rest
|
||||
.post(api_client.base_url.join("apps/cookbook/import")?)
|
||||
.json(&serde_json::json!({
|
||||
"url": url.as_ref(),
|
||||
}))
|
||||
.send()
|
||||
.await?;
|
||||
if ![StatusCode::OK, StatusCode::CONFLICT].contains(&response.status()) {
|
||||
anyhow::bail!(
|
||||
"Unable to import recipe {}, received status code {}",
|
||||
url.as_ref(),
|
||||
response.status()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
14
src/commands/init.rs
Normal file
14
src/commands/init.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use {crate::config::Config, anyhow::Result, reqwest::Url};
|
||||
|
||||
pub async fn with(configuration: &mut Config, server: &str) -> Result<()> {
|
||||
tokio::task::block_in_place(move || -> anyhow::Result<()> {
|
||||
configuration
|
||||
.credentials
|
||||
.add(Url::parse(server)?)
|
||||
.expect("Unable to authenticate to NextCloud instance");
|
||||
Ok(())
|
||||
})
|
||||
}
|
7
src/commands/mod.rs
Normal file
7
src/commands/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
pub mod import;
|
||||
pub mod init;
|
||||
pub mod schedule;
|
||||
pub mod schedule_csv;
|
14
src/commands/schedule.rs
Normal file
14
src/commands/schedule.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use {crate::api_client::ApiClient, crate::recipe, anyhow::Result};
|
||||
|
||||
pub async fn with(api_client: &ApiClient) -> Result<()> {
|
||||
let recipes = api_client
|
||||
.rest
|
||||
.get(api_client.base_url.join("apps/cookbook/api/recipes")?)
|
||||
.send()
|
||||
.await?;
|
||||
println!("{:#?}", recipes.json::<Vec<recipe::Metadata>>().await?);
|
||||
todo!();
|
||||
}
|
181
src/commands/schedule_csv.rs
Normal file
181
src/commands/schedule_csv.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use {
|
||||
crate::api_client::ApiClient,
|
||||
crate::commands::import,
|
||||
crate::constants,
|
||||
crate::recipe,
|
||||
anyhow::Result,
|
||||
chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime},
|
||||
chrono::{DateTime, Local, TimeZone},
|
||||
futures::future::try_join_all,
|
||||
ics::properties as calprop,
|
||||
ics::Event,
|
||||
std::collections::{HashMap, HashSet},
|
||||
std::iter::Iterator,
|
||||
std::path::Path,
|
||||
};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct CsvRecord {
|
||||
day: NaiveDate,
|
||||
lunch: String,
|
||||
dinner: String,
|
||||
}
|
||||
|
||||
#[derive(strum_macros::Display)]
|
||||
enum Meal {
|
||||
Lunch,
|
||||
Dinner,
|
||||
}
|
||||
|
||||
pub async fn with(api_client: &ApiClient, calendar: &str, csv_file: &Path) -> Result<()> {
|
||||
let mut csv = csv::Reader::from_path(csv_file)?;
|
||||
let records = csv.deserialize::<CsvRecord>().flatten().collect::<Vec<_>>();
|
||||
|
||||
let recipe_urls = urls_from_csv(records.iter())?;
|
||||
import::with(&api_client, recipe_urls.into_iter()).await?;
|
||||
let recipes = get_all_recipes(&api_client).await?;
|
||||
|
||||
let events = records
|
||||
.iter()
|
||||
.flat_map(|r| {
|
||||
let lunch = recipes.get(&r.lunch);
|
||||
let dinner = recipes.get(&r.dinner);
|
||||
|
||||
let events = [
|
||||
lunch.map(|recipe| to_event(r.day, Meal::Lunch, recipe)),
|
||||
dinner.map(|recipe| to_event(r.day, Meal::Dinner, recipe)),
|
||||
];
|
||||
|
||||
events
|
||||
})
|
||||
.flatten();
|
||||
|
||||
publish_events(&api_client, calendar, events)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_event(date: NaiveDate, meal: Meal, recipe: &recipe::Recipe) -> Event {
|
||||
// TODO: this is momentarily hardcoded, should be an option
|
||||
let meal_time = match meal {
|
||||
Meal::Lunch => NaiveTime::from_hms(12, 00, 00),
|
||||
Meal::Dinner => NaiveTime::from_hms(19, 00, 00),
|
||||
};
|
||||
|
||||
let uid = format!(
|
||||
"{}-{}@{}.montecristosoftware.eu",
|
||||
date,
|
||||
meal,
|
||||
env!("CARGO_PKG_NAME")
|
||||
);
|
||||
|
||||
let end_time = NaiveDateTime::new(date, meal_time);
|
||||
let start_time = end_time - recipe.total_time();
|
||||
|
||||
let mut event = Event::new(uid, dt_fmt(&Local::now()));
|
||||
event.push(calprop::Summary::new(&recipe.name));
|
||||
event.push(calprop::Description::new(format!("cookbook@{}", recipe.id)));
|
||||
event.push(calprop::Location::new(&recipe.url));
|
||||
event.push(calprop::DtStart::new(dt_fmt(
|
||||
&Local.from_local_datetime(&start_time).unwrap(),
|
||||
)));
|
||||
event.push(calprop::DtEnd::new(dt_fmt(
|
||||
&Local.from_local_datetime(&end_time).unwrap(),
|
||||
)));
|
||||
|
||||
let alarm = ics::Alarm::display(
|
||||
calprop::Trigger::new("-P15M"),
|
||||
calprop::Description::new(&recipe.name),
|
||||
);
|
||||
event.add_alarm(alarm);
|
||||
|
||||
event
|
||||
}
|
||||
|
||||
fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result<HashSet<String>>
|
||||
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 !r.dinner.is_empty() {
|
||||
set.insert(r.dinner.clone());
|
||||
}
|
||||
set
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn dt_fmt(datetime: &DateTime<Local>) -> String {
|
||||
datetime.format("%Y%m%dT%H%M%SZ").to_string()
|
||||
}
|
||||
|
||||
async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, recipe::Recipe>> {
|
||||
let metadata = api_client
|
||||
.rest
|
||||
.get(api_client.base_url.join("apps/cookbook/api/recipes")?)
|
||||
.send()
|
||||
.await?
|
||||
.json::<Vec<recipe::Metadata>>()
|
||||
.await?;
|
||||
|
||||
let recipes = metadata.iter().map(|rm| async {
|
||||
let response = api_client
|
||||
.rest
|
||||
.get(
|
||||
api_client
|
||||
.base_url
|
||||
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id))
|
||||
.unwrap(),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.expect(&format!(
|
||||
"Cannot fetch recipe {} with id {}",
|
||||
rm.name, rm.id
|
||||
));
|
||||
response.json::<recipe::Recipe>().await
|
||||
});
|
||||
|
||||
let recipes = try_join_all(recipes).await?;
|
||||
Ok(HashMap::from_iter(
|
||||
recipes.into_iter().map(|r| (r.url.clone(), r)),
|
||||
))
|
||||
}
|
||||
|
||||
fn publish_events<'a, EventsIter>(
|
||||
api_client: &ApiClient,
|
||||
calendar: &str,
|
||||
events: EventsIter,
|
||||
) -> Result<()>
|
||||
where
|
||||
EventsIter: Iterator<Item = Event<'a>>,
|
||||
{
|
||||
let calendar_url = api_client.base_url.join(&format!(
|
||||
"remote.php/dav/calendars/{}/{}/",
|
||||
&api_client.username,
|
||||
calendar.to_lowercase().as_str().replace(" ", "-")
|
||||
));
|
||||
|
||||
let cal = ics::ICalendar::new(
|
||||
"2.0",
|
||||
format!(
|
||||
"-//{}//NONSGML {}//EN",
|
||||
constants::VENDOR,
|
||||
env!("CARGO_PKG_NAME")
|
||||
),
|
||||
);
|
||||
let cal = events.fold(cal, |mut cal, e| {
|
||||
cal.add_event(e);
|
||||
cal
|
||||
});
|
||||
|
||||
println!("CALENDAR: \n {:?}", cal);
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue