diff --git a/Cargo.lock b/Cargo.lock index 3122fb6..2183192 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,6 +403,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "domain" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", + "uuid", +] + [[package]] name = "drain_filter_polyfill" version = "0.1.3" @@ -441,6 +450,16 @@ dependencies = [ "syn", ] +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fnv" version = "1.0.7" @@ -773,6 +792,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.2" @@ -1496,9 +1521,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", @@ -1526,6 +1551,33 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "services" +version = "0.1.0" +dependencies = [ + "chrono", + "domain", + "eyre", + "serde", + "serde_json", + "serde_yaml", + "tokio", + "uuid", +] + [[package]] name = "sha2" version = "0.10.6" @@ -1603,6 +1655,7 @@ dependencies = [ "chrono", "console_error_panic_hook", "console_log", + "domain", "lazy_static", "leptos", "leptos_axum", @@ -1610,6 +1663,7 @@ dependencies = [ "leptos_router", "log", "serde", + "services", "simple_logger", "thiserror", "tokio", @@ -1942,6 +1996,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unsafe-libyaml" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c" + [[package]] name = "url" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index 0607de5..9e8a509 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,7 @@ +[workspace] +members = [".", "crates/services", "crates/domain"] + + [package] name = "ssr_modes_axum" version = "0.1.0" @@ -27,6 +31,9 @@ wasm-bindgen = "0.2" chrono = { version = "0.4.23", features = ["serde"] } uuid = { version = "1.3.0", features = ["v4", "wasm-bindgen", "js", "serde"] } +domain = { path = "crates/domain" } +services = { path = "crates/services", optional = true } + [features] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] ssr = [ @@ -38,6 +45,7 @@ ssr = [ "leptos_meta/ssr", "leptos_router/ssr", "dep:leptos_axum", + "dep:services", ] [package.metadata.leptos] diff --git a/articles/events/2023-03-06-gammeldags-oksesteg.md b/articles/events/2023-03-06-gammeldags-oksesteg.md new file mode 100644 index 0000000..9910ccc --- /dev/null +++ b/articles/events/2023-03-06-gammeldags-oksesteg.md @@ -0,0 +1,13 @@ +--- +coverImage: + url: "https://cdn-rdb.arla.com/Files/arla-dk/2010638351/0606cf14-3972-4abb-b2c8-faa3249de170.jpg?crop=(0,482,0,-117)&w=1269&h=715&mode=crop&ak=6826258c&hm=f35b5bfe" + alt: billede af oksesteg +name: Gammeldags oksesteg +description: | + God gammeldags oksesteg med en intens og fyldig brun sauce. Gammeldags oksesteg + er rigtig simremad som gør de fleste glade. Så server en gammeldags oksesteg for + din gæster... både de unge og de gamle. +time: 2023-03-06 +--- + +Some article diff --git a/crates/domain/Cargo.toml b/crates/domain/Cargo.toml new file mode 100644 index 0000000..ed233f6 --- /dev/null +++ b/crates/domain/Cargo.toml @@ -0,0 +1,14 @@ +[lib] +crate-type = ["cdylib", "rlib"] + +[package] +name = "domain" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4.23", features = ["serde"] } +serde = { version = "1.0.152", features = ["derive"] } +uuid = { version = "1.3.0", features = ["v4", "wasm-bindgen", "js", "serde"] } diff --git a/src/models.rs b/crates/domain/src/lib.rs similarity index 93% rename from src/models.rs rename to crates/domain/src/lib.rs index 717510c..3765010 100644 --- a/src/models.rs +++ b/crates/domain/src/lib.rs @@ -25,7 +25,7 @@ pub struct Event { pub cover_image: Option, pub name: String, pub description: Option, - pub time: chrono::DateTime, + pub time: chrono::NaiveDate, pub recipe_id: Option, pub images: Vec, pub metadata: Option, @@ -37,7 +37,7 @@ pub struct EventOverview { pub cover_image: Option, pub name: String, pub description: Option, - pub time: chrono::DateTime, + pub time: chrono::NaiveDate, } impl From for EventOverview { diff --git a/crates/services/.gitignore b/crates/services/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/crates/services/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/crates/services/Cargo.toml b/crates/services/Cargo.toml new file mode 100644 index 0000000..c465fae --- /dev/null +++ b/crates/services/Cargo.toml @@ -0,0 +1,19 @@ +[lib] +crate-type = ["cdylib", "rlib"] + +[package] +name = "services" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4.23", features = ["serde"] } +domain = { path = "../domain" } +eyre = "0.6.8" +serde = { version = "1.0.152", features = ["derive"] } +serde_json = "1.0.94" +serde_yaml = "0.9.19" +tokio = { version = "1.26.0", features = ["full"] } +uuid = { version = "1.3.0", features = ["v4", "serde"] } diff --git a/crates/services/src/lib.rs b/crates/services/src/lib.rs new file mode 100644 index 0000000..96040f8 --- /dev/null +++ b/crates/services/src/lib.rs @@ -0,0 +1,143 @@ +use std::path::PathBuf; + +use domain::{Event, Image, Metadata}; +use serde::{Deserialize, Serialize}; + +pub struct EventStore { + pub path: PathBuf, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RawImage { + pub url: String, + pub alt: String, + pub metadata: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RawEvent { + pub cover_image: Option, + pub name: String, + pub description: Option, + #[serde(with = "short_time_stamp")] + pub time: chrono::NaiveDate, + pub recipe_id: Option, + //pub images: Vec, + pub metadata: Option, + #[serde(skip)] + pub content: String, +} + +mod short_time_stamp { + use chrono::{DateTime, NaiveDate, TimeZone, Utc}; + use serde::{self, Deserialize, Deserializer, Serializer}; + + const FORMAT: &'static str = "%Y-%m-%d"; + + pub fn serialize(date: &NaiveDate, serializer: S) -> Result + where + S: Serializer, + { + let s = format!("{}", date.format(FORMAT)); + serializer.serialize_str(&s) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + NaiveDate::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom) + } +} + +impl From for Event { + fn from(value: RawEvent) -> Self { + Self { + id: uuid::Uuid::new_v4(), + cover_image: value.cover_image.map(|ci| ci.into()), + name: value.name, + description: value.description, + time: value.time, + recipe_id: value.recipe_id, + images: vec![], + metadata: value.metadata, + } + } +} + +impl From for Image { + fn from(value: RawImage) -> Self { + Self { + id: uuid::Uuid::new_v4(), + url: value.url, + alt: value.alt, + metadata: value.metadata, + } + } +} + +impl EventStore { + pub fn new(path: PathBuf) -> Self { + Self { path } + } + + pub async fn get_upcoming_events(&self) -> eyre::Result> { + let mut event_path = self.path.clone(); + event_path.push("events"); + let mut dir = tokio::fs::read_dir(event_path).await?; + + let mut events = vec![]; + + while let Ok(Some(entry)) = dir.next_entry().await { + let metadata = entry.metadata().await?; + if metadata.is_file() { + let file = tokio::fs::read(entry.path()).await?; + let content = std::str::from_utf8(&file)?; + if content.starts_with("---\n") { + let after_marker = &content[4..]; + if let Some(marker_end) = after_marker.find("---\n") { + let raw_front_matter = &content[4..marker_end + 4]; + let mut raw_event: RawEvent = serde_yaml::from_str(raw_front_matter)?; + raw_event.content = content[marker_end + 4..].to_string(); + + events.push(raw_event.into()) + } + } + } + } + + Ok(events) + } +} + +impl Default for EventStore { + fn default() -> Self { + Self { + path: PathBuf::from("articles"), + } + } +} + +#[cfg(test)] +mod test { + use domain::Event; + + use crate::RawEvent; + + #[test] + fn can_parse_event() { + let raw = r#"coverImage: + url: "https://cdn-rdb.arla.com/Files/arla-dk/2010638351/0606cf14-3972-4abb-b2c8-faa3249de170.jpg?crop=(0,482,0,-117)&w=1269&h=715&mode=crop&ak=6826258c&hm=f35b5bfe" + alt: billede af oksesteg +name: Gammeldags oksesteg +description: | + God gammeldags oksesteg med en intens og fyldig brun sauce. Gammeldags oksesteg + er rigtig simremad som gør de fleste glade. Så server en gammeldags oksesteg for + din gæster... både de unge og de gamle. +time: 2023-03-06"#; + + let raw_event: RawEvent = serde_yaml::from_str(raw).unwrap(); + let _: Event = raw_event.into(); + } +} diff --git a/src/api/events.rs b/src/api/events.rs index a15c082..fdf6d5f 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -2,7 +2,7 @@ use lazy_static::lazy_static; use leptos::*; use serde::{Deserialize, Serialize}; -use crate::models::{Event, EventOverview, Image}; +use domain::{Event, EventOverview, Image}; lazy_static! { static ref EVENTS: Vec = vec![ @@ -18,7 +18,8 @@ lazy_static! { description: Some("Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.".into()), time: chrono::Utc::now() .checked_add_days(chrono::Days::new(1)) - .unwrap(), + .unwrap() + .date_naive(), recipe_id: None, images: vec![], metadata: None, @@ -35,7 +36,8 @@ lazy_static! { description: Some("Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.".into()), time: chrono::Utc::now() .checked_add_days(chrono::Days::new(4)) - .unwrap(), + .unwrap() + .date_naive(), recipe_id: None, images: vec![], metadata: None, @@ -52,7 +54,8 @@ lazy_static! { description: Some("description".into()), time: chrono::Utc::now() .checked_sub_days(chrono::Days::new(2)) - .unwrap(), + .unwrap() + .date_naive(), recipe_id: None, images: vec![], metadata: None, @@ -71,15 +74,19 @@ pub async fn get_upcoming_events() -> Result Result { let current_time = chrono::Utc::now(); - let mut events: Vec = EVENTS + let es = services::EventStore::default(); + + let events = es + .get_upcoming_events() + .await + .map_err(|e| ServerFnError::ServerError(e.to_string()))? .iter() - .filter(|data| data.time > current_time) .map(|data| data.clone().into()) .collect(); - events.sort_by(|a, b| a.time.cmp(&b.time)); Ok(UpcomingEventsOverview { events }) } @@ -91,6 +98,7 @@ pub async fn get_full_event(event_id: uuid::Uuid) -> Result, Serve get_full_event_fn(event_id).await } +#[cfg(feature = "ssr")] async fn get_full_event_fn(event_id: uuid::Uuid) -> Result, ServerFnError> { let event = EVENTS .iter() diff --git a/src/app.rs b/src/app.rs index ec08a44..496ac66 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,20 +10,20 @@ pub fn App(cx: Scope) -> impl IntoView { provide_meta_context(cx); view! { cx, - - + <Stylesheet id="leptos" href="/pkg/ssr_modes.css" /> + <Title text="Bitebuds" /> - <Router> - <div class="app grid lg:grid-cols-[25%,50%,25%] sm:grid-cols-[10%,80%,10%] grid-cols-[5%,90%,5%]"> - <main class="main col-start-2"> - <div class="pt-4"> - <h1 class="font-semibold text-xl tracking-wide">"Bitebuds"</h1> - <Routes> - <Route path="" view=|cx| view! { cx, <HomePage /> }/> - </Routes> - </div> - </main> - </div> - </Router> + <Router> + <div class="app grid lg:grid-cols-[25%,50%,25%] sm:grid-cols-[10%,80%,10%] grid-cols-[5%,90%,5%]"> + <main class="main col-start-2"> + <div class="pt-4"> + <h1 class="font-semibold text-xl tracking-wide">"Bitebuds"</h1> + <Routes> + <Route path="" view=|cx| view! { cx, <HomePage /> }/> + </Routes> + </div> + </main> + </div> + </Router> } } diff --git a/src/components/day.rs b/src/components/day.rs index 0c84ec7..13dfd9e 100644 --- a/src/components/day.rs +++ b/src/components/day.rs @@ -2,7 +2,7 @@ use chrono::Datelike; use leptos::*; use crate::api::events::*; -use crate::models::{EventOverview, Image}; +use domain::{EventOverview, Image}; #[component] pub fn Day( diff --git a/src/lib.rs b/src/lib.rs index ae4dc0c..a2781ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,6 @@ pub mod api; pub mod app; mod components; pub mod fallback; -mod models; mod pages; use cfg_if::cfg_if;