mirror of
https://github.com/kjuulh/bitebuds.git
synced 2025-08-09 15:43:27 +02:00
feat: with local store
This commit is contained in:
14
crates/domain/Cargo.toml
Normal file
14
crates/domain/Cargo.toml
Normal file
@@ -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"] }
|
53
crates/domain/src/lib.rs
Normal file
53
crates/domain/src/lib.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Metadata(HashMap<String, String>);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Recipe {
|
||||
pub id: uuid::Uuid,
|
||||
pub metadata: Option<Metadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Image {
|
||||
pub id: uuid::Uuid,
|
||||
pub url: String,
|
||||
pub alt: String,
|
||||
pub metadata: Option<Metadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Event {
|
||||
pub id: uuid::Uuid,
|
||||
pub cover_image: Option<Image>,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub time: chrono::NaiveDate,
|
||||
pub recipe_id: Option<uuid::Uuid>,
|
||||
pub images: Vec<Image>,
|
||||
pub metadata: Option<Metadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct EventOverview {
|
||||
pub id: uuid::Uuid,
|
||||
pub cover_image: Option<Image>,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub time: chrono::NaiveDate,
|
||||
}
|
||||
|
||||
impl From<Event> for EventOverview {
|
||||
fn from(value: Event) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
cover_image: value.cover_image,
|
||||
name: value.name,
|
||||
description: value.description,
|
||||
time: value.time,
|
||||
}
|
||||
}
|
||||
}
|
2
crates/services/.gitignore
vendored
Normal file
2
crates/services/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
19
crates/services/Cargo.toml
Normal file
19
crates/services/Cargo.toml
Normal file
@@ -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"] }
|
143
crates/services/src/lib.rs
Normal file
143
crates/services/src/lib.rs
Normal file
@@ -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<Metadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RawEvent {
|
||||
pub cover_image: Option<RawImage>,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
#[serde(with = "short_time_stamp")]
|
||||
pub time: chrono::NaiveDate,
|
||||
pub recipe_id: Option<uuid::Uuid>,
|
||||
//pub images: Vec<RawImage>,
|
||||
pub metadata: Option<Metadata>,
|
||||
#[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<S>(date: &NaiveDate, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let s = format!("{}", date.format(FORMAT));
|
||||
serializer.serialize_str(&s)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
NaiveDate::parse_from_str(&s, FORMAT).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawEvent> 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<RawImage> 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<Vec<Event>> {
|
||||
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();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user