mirror of
https://git.alemi.dev/guestbook.rs.git
synced 2024-12-19 02:54:52 +01:00
fix: made rustc happy, now compiles
This commit is contained in:
parent
2e97bffc82
commit
d8fa2173cb
4 changed files with 33 additions and 47 deletions
|
@ -60,8 +60,11 @@ async fn main() {
|
||||||
|
|
||||||
let storage = Box::new(JsonFileStorageStrategy::new("./storage.json"));
|
let storage = Box::new(JsonFileStorageStrategy::new("./storage.json"));
|
||||||
|
|
||||||
let state = Context::new(storage)
|
let overrides = CliServeOverrides { author, public };
|
||||||
.register(Box::new(ConsoleTracingNotifier {}));
|
|
||||||
|
let mut state = Context::new(storage, overrides);
|
||||||
|
|
||||||
|
state.register(Box::new(ConsoleTracingNotifier {}));
|
||||||
|
|
||||||
let router = routes::create_router_with_app_routes(state);
|
let router = routes::create_router_with_app_routes(state);
|
||||||
|
|
||||||
|
|
14
src/model.rs
14
src/model.rs
|
@ -35,10 +35,10 @@ impl From<Page> for PageView {
|
||||||
hasher.update(page.contact.as_deref().unwrap_or(&Uuid::new_v4().to_string()).as_bytes());
|
hasher.update(page.contact.as_deref().unwrap_or(&Uuid::new_v4().to_string()).as_bytes());
|
||||||
let avatar = format!("{:x}", hasher.finalize());
|
let avatar = format!("{:x}", hasher.finalize());
|
||||||
|
|
||||||
let url = match page.contact {
|
let url = match page.contact.as_deref() {
|
||||||
None => None,
|
None => None,
|
||||||
Some(c) => if c.starts_with("http") {
|
Some(c) => if c.starts_with("http") {
|
||||||
Some(c)
|
Some(c.to_string())
|
||||||
} else if c.contains('@') {
|
} else if c.contains('@') {
|
||||||
Some(format!("mailto:{}", c))
|
Some(format!("mailto:{}", c))
|
||||||
} else if c.contains('.') {
|
} else if c.contains('.') {
|
||||||
|
@ -74,12 +74,12 @@ pub struct PageInsertion {
|
||||||
|
|
||||||
impl PageInsertion {
|
impl PageInsertion {
|
||||||
pub fn sanitize(&mut self) {
|
pub fn sanitize(&mut self) {
|
||||||
self.author = self.author.map(|x| html_escape::encode_safe(&x.chars().take(AUTHOR_MAX_CHARS).collect::<String>()).to_string());
|
self.author = self.author.as_mut().map(|x| html_escape::encode_safe(&x.chars().take(AUTHOR_MAX_CHARS).collect::<String>()).to_string());
|
||||||
self.contact = self.contact.map(|x| html_escape::encode_safe(&x.chars().take(CONTACT_MAX_CHARS).collect::<String>()).to_string());
|
self.contact = self.contact.as_mut().map(|x| html_escape::encode_safe(&x.chars().take(CONTACT_MAX_CHARS).collect::<String>()).to_string());
|
||||||
self.body = html_escape::encode_safe(&self.body.chars().take(BODY_MAX_CHARS).collect::<String>()).to_string();
|
self.body = html_escape::encode_safe(&self.body.chars().take(BODY_MAX_CHARS).collect::<String>()).to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert(mut self, overrides: crate::CliServeOverrides) -> Page {
|
pub fn convert(mut self, overrides: &crate::CliServeOverrides) -> Page {
|
||||||
self.sanitize();
|
self.sanitize();
|
||||||
|
|
||||||
let mut page = Page {
|
let mut page = Page {
|
||||||
|
@ -90,8 +90,8 @@ impl PageInsertion {
|
||||||
public: true,
|
public: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(author) = overrides.author {
|
if let Some(author) = &overrides.author {
|
||||||
page.author = author;
|
page.author = author.to_string();
|
||||||
}
|
}
|
||||||
if let Some(public) = overrides.public {
|
if let Some(public) = overrides.public {
|
||||||
page.public = public;
|
page.public = public;
|
||||||
|
|
|
@ -1,71 +1,54 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{Json, Form, Router, routing::{put, post, get}, extract::{State, Query}, response::Redirect};
|
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 {
|
pub fn create_router_with_app_routes(state: Context) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/api", get(get_suggestion))
|
.route("/api", get(get_suggestion))
|
||||||
.route("/api", post(send_suggestion_form))
|
.route("/api", post(send_suggestion_form))
|
||||||
.route("/api", put(send_suggestion_json))
|
.route("/api", put(send_suggestion_json))
|
||||||
.with_state(Arc::new(RwLock::new(state)))
|
.with_state(Arc::new(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
type SafeContext = Arc<RwLock<Context>>;
|
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
providers: Vec<Box<dyn NotificationProcessor<Page>>>,
|
providers: Vec<Box<dyn NotificationProcessor<Page>>>,
|
||||||
storage: Box<dyn StorageStrategy<Page>>,
|
storage: Box<dyn StorageStrategy<Page>>,
|
||||||
|
overrides: CliServeOverrides,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn new(storage: Box<dyn StorageStrategy<Page>>) -> Self {
|
pub fn new(storage: Box<dyn StorageStrategy<Page>>, overrides: CliServeOverrides) -> Self {
|
||||||
Context { providers: Vec::new(), storage }
|
Context { providers: Vec::new(), storage, overrides }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(mut self, notifier: Box<dyn NotificationProcessor<Page>>) -> Self {
|
pub fn register(&mut self, notifier: Box<dyn NotificationProcessor<Page>>) {
|
||||||
self.providers.push(notifier);
|
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<Redirect, String> {
|
async fn send_suggestion(payload: PageInsertion, state: Arc<Context>) -> Result<Redirect, String> {
|
||||||
let page = Page::from(payload);
|
let page = payload.convert(&state.overrides);
|
||||||
// lock state, process and archive new page
|
for p in state.providers.iter() {
|
||||||
let mut lock = state.write().await;
|
p.process(&page).await;
|
||||||
lock.process(&page).await;
|
}
|
||||||
match lock.storage.archive(page).await {
|
match state.storage.archive(page).await {
|
||||||
Ok(()) => Ok(Redirect::to("/")),
|
Ok(()) => Ok(Redirect::to("/")),
|
||||||
Err(e) => Err(e.to_string()),
|
Err(e) => Err(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_suggestion_json(State(state): State<SafeContext>, Json(payload): Json<PageInsertion>) -> Result<Redirect, String> { send_suggestion(payload, state).await }
|
async fn send_suggestion_json(State(state): State<Arc<Context>>, Json(payload): Json<PageInsertion>) -> Result<Redirect, String> { send_suggestion(payload, state).await }
|
||||||
async fn send_suggestion_form(State(state): State<SafeContext>, Form(payload): Form<PageInsertion>) -> Result<Redirect, String> { send_suggestion(payload, state).await }
|
async fn send_suggestion_form(State(state): State<Arc<Context>>, Form(payload): Form<PageInsertion>) -> Result<Redirect, String> { send_suggestion(payload, state).await }
|
||||||
|
|
||||||
|
|
||||||
async fn get_suggestion(State(state): State<SafeContext>, Query(page): Query<PageOptions>) -> Result<Json<Vec<Page>>, String> {
|
async fn get_suggestion(State(state): State<Arc<Context>>, Query(page): Query<PageOptions>) -> Result<Json<Vec<Page>>, String> {
|
||||||
let offset = page.offset.unwrap_or(0);
|
let offset = page.offset.unwrap_or(0);
|
||||||
let limit = std::cmp::min(page.limit.unwrap_or(20), 20);
|
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)),
|
Ok(x) => Ok(Json(x)),
|
||||||
Err(e) => Err(e.to_string()),
|
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()
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub enum StorageStrategyError {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait StorageStrategy<T> : Send + Sync {
|
pub trait StorageStrategy<T> : 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<Vec<T>, StorageStrategyError>;
|
async fn extract(&self, offset: usize, window: usize) -> Result<Vec<T>, StorageStrategyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,19 +33,19 @@ impl JsonFileStorageStrategy {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl StorageStrategy<Page> for JsonFileStorageStrategy {
|
impl StorageStrategy<Page> 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 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<Page> = serde_json::from_str(&file_content)?;
|
let mut current_content : Vec<Page> = serde_json::from_str(&file_content)?;
|
||||||
current_content.push(payload);
|
current_content.push(payload);
|
||||||
let updated_content = serde_json::to_string(¤t_content)?;
|
let updated_content = serde_json::to_string(¤t_content)?;
|
||||||
std::fs::write(*path, updated_content)?;
|
std::fs::write(&*path, updated_content)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn extract(&self, offset: usize, window: usize) -> Result<Vec<Page>, StorageStrategyError> {
|
async fn extract(&self, offset: usize, window: usize) -> Result<Vec<Page>, StorageStrategyError> {
|
||||||
let path = self.path.read().await;
|
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<Page> = serde_json::from_str(&file_content)?;
|
let current_content : Vec<Page> = serde_json::from_str(&file_content)?;
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for sugg in current_content.iter().rev().skip(offset) {
|
for sugg in current_content.iter().rev().skip(offset) {
|
||||||
|
|
Loading…
Reference in a new issue