diff --git a/src/main.rs b/src/main.rs index 0e3b479..dfaa490 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,8 +60,11 @@ async fn main() { let storage = Box::new(JsonFileStorageStrategy::new("./storage.json")); - let state = Context::new(storage) - .register(Box::new(ConsoleTracingNotifier {})); + let overrides = CliServeOverrides { author, public }; + + let mut state = Context::new(storage, overrides); + + state.register(Box::new(ConsoleTracingNotifier {})); let router = routes::create_router_with_app_routes(state); diff --git a/src/model.rs b/src/model.rs index 4b8b9eb..d969dea 100644 --- a/src/model.rs +++ b/src/model.rs @@ -35,10 +35,10 @@ impl From for PageView { hasher.update(page.contact.as_deref().unwrap_or(&Uuid::new_v4().to_string()).as_bytes()); let avatar = format!("{:x}", hasher.finalize()); - let url = match page.contact { + let url = match page.contact.as_deref() { None => None, Some(c) => if c.starts_with("http") { - Some(c) + Some(c.to_string()) } else if c.contains('@') { Some(format!("mailto:{}", c)) } else if c.contains('.') { @@ -74,12 +74,12 @@ pub struct PageInsertion { impl PageInsertion { pub fn sanitize(&mut self) { - self.author = self.author.map(|x| html_escape::encode_safe(&x.chars().take(AUTHOR_MAX_CHARS).collect::()).to_string()); - self.contact = self.contact.map(|x| html_escape::encode_safe(&x.chars().take(CONTACT_MAX_CHARS).collect::()).to_string()); + self.author = self.author.as_mut().map(|x| html_escape::encode_safe(&x.chars().take(AUTHOR_MAX_CHARS).collect::()).to_string()); + self.contact = self.contact.as_mut().map(|x| html_escape::encode_safe(&x.chars().take(CONTACT_MAX_CHARS).collect::()).to_string()); self.body = html_escape::encode_safe(&self.body.chars().take(BODY_MAX_CHARS).collect::()).to_string(); } - pub fn convert(mut self, overrides: crate::CliServeOverrides) -> Page { + pub fn convert(mut self, overrides: &crate::CliServeOverrides) -> Page { self.sanitize(); let mut page = Page { @@ -90,8 +90,8 @@ impl PageInsertion { public: true, }; - if let Some(author) = overrides.author { - page.author = author; + if let Some(author) = &overrides.author { + page.author = author.to_string(); } if let Some(public) = overrides.public { page.public = public; diff --git a/src/routes.rs b/src/routes.rs index feb6439..b1d9798 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,71 +1,54 @@ use std::sync::Arc; use axum::{Json, Form, Router, routing::{put, post, get}, extract::{State, Query}, response::Redirect}; -use chrono::Utc; -use md5::{Md5, Digest}; -use tokio::sync::RwLock; -use uuid::Uuid; -use crate::{notifications::NotificationProcessor, model::{Page, PageOptions, PageInsertion}, storage::StorageStrategy}; +use crate::{notifications::NotificationProcessor, model::{Page, PageOptions, PageInsertion}, storage::StorageStrategy, CliServeOverrides}; pub fn create_router_with_app_routes(state: Context) -> Router { Router::new() .route("/api", get(get_suggestion)) .route("/api", post(send_suggestion_form)) .route("/api", put(send_suggestion_json)) - .with_state(Arc::new(RwLock::new(state))) + .with_state(Arc::new(state)) } -type SafeContext = Arc>; - pub struct Context { providers: Vec>>, storage: Box>, + overrides: CliServeOverrides, } impl Context { - pub fn new(storage: Box>) -> Self { - Context { providers: Vec::new(), storage } + pub fn new(storage: Box>, overrides: CliServeOverrides) -> Self { + Context { providers: Vec::new(), storage, overrides } } - pub fn register(mut self, notifier: Box>) -> Self { + pub fn register(&mut self, notifier: Box>) { self.providers.push(notifier); - self - } - - async fn process(&self, x: &Page) { - for p in self.providers.iter() { - p.process(x).await; - } } } -async fn send_suggestion(payload: PageInsertion, state: SafeContext) -> Result { - let page = Page::from(payload); - // lock state, process and archive new page - let mut lock = state.write().await; - lock.process(&page).await; - match lock.storage.archive(page).await { +async fn send_suggestion(payload: PageInsertion, state: Arc) -> Result { + let page = payload.convert(&state.overrides); + for p in state.providers.iter() { + p.process(&page).await; + } + match state.storage.archive(page).await { Ok(()) => Ok(Redirect::to("/")), Err(e) => Err(e.to_string()), } } -async fn send_suggestion_json(State(state): State, Json(payload): Json) -> Result { send_suggestion(payload, state).await } -async fn send_suggestion_form(State(state): State, Form(payload): Form) -> Result { send_suggestion(payload, state).await } +async fn send_suggestion_json(State(state): State>, Json(payload): Json) -> Result { send_suggestion(payload, state).await } +async fn send_suggestion_form(State(state): State>, Form(payload): Form) -> Result { send_suggestion(payload, state).await } -async fn get_suggestion(State(state): State, Query(page): Query) -> Result>, String> { +async fn get_suggestion(State(state): State>, Query(page): Query) -> Result>, String> { let offset = page.offset.unwrap_or(0); let limit = std::cmp::min(page.limit.unwrap_or(20), 20); - match state.read().await.storage.extract(offset, limit).await { + match state.storage.extract(offset, limit).await { Ok(x) => Ok(Json(x)), Err(e) => Err(e.to_string()), } } - -fn limit_string(s: &str, l: usize) -> String { - // TODO is there a better way? slicing doesn't work when l > s.len - s.chars().take(l).collect() -} diff --git a/src/storage.rs b/src/storage.rs index 4a52f7d..325e0d3 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -13,7 +13,7 @@ pub enum StorageStrategyError { #[async_trait::async_trait] pub trait StorageStrategy : Send + Sync { - async fn archive(&mut self, payload: T) -> Result<(), StorageStrategyError>; + async fn archive(&self, payload: T) -> Result<(), StorageStrategyError>; async fn extract(&self, offset: usize, window: usize) -> Result, StorageStrategyError>; } @@ -33,19 +33,19 @@ impl JsonFileStorageStrategy { #[async_trait::async_trait] impl StorageStrategy for JsonFileStorageStrategy { - async fn archive(&mut self, payload: Page) -> Result<(), StorageStrategyError> { + async fn archive(&self, payload: Page) -> Result<(), StorageStrategyError> { let path = self.path.write().await; - let file_content = std::fs::read_to_string(*path)?; + let file_content = std::fs::read_to_string(&*path)?; let mut current_content : Vec = serde_json::from_str(&file_content)?; current_content.push(payload); let updated_content = serde_json::to_string(¤t_content)?; - std::fs::write(*path, updated_content)?; + std::fs::write(&*path, updated_content)?; Ok(()) } async fn extract(&self, offset: usize, window: usize) -> Result, StorageStrategyError> { let path = self.path.read().await; - let file_content = std::fs::read_to_string(*path)?; + let file_content = std::fs::read_to_string(&*path)?; let current_content : Vec = serde_json::from_str(&file_content)?; let mut out = Vec::new(); for sugg in current_content.iter().rev().skip(offset) {