2023-12-22 23:37:23 +01:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use axum::{http::StatusCode, Json, Form, Router, routing::{put, post, get}, extract::{State, Query}, response::IntoResponse};
|
2023-12-23 03:15:06 +01:00
|
|
|
use chrono::Utc;
|
|
|
|
use md5::{Md5, Digest};
|
2023-12-22 23:37:23 +01:00
|
|
|
use tokio::sync::RwLock;
|
2023-12-23 03:15:06 +01:00
|
|
|
use uuid::Uuid;
|
2023-12-22 23:37:23 +01:00
|
|
|
|
2023-12-23 03:14:18 +01:00
|
|
|
use crate::{notifications::NotificationProcessor, model::{GuestBookPage, Acknowledgement, PageOptions, Insertion}, storage::StorageStrategy};
|
2023-12-22 23:37:23 +01:00
|
|
|
|
|
|
|
pub fn create_router_with_app_routes(state: Context) -> Router {
|
|
|
|
Router::new()
|
2023-12-23 01:26:31 +01:00
|
|
|
.route("/api", get(get_suggestion))
|
|
|
|
.route("/api", post(send_suggestion_form))
|
|
|
|
.route("/api", put(send_suggestion_json))
|
2023-12-22 23:37:23 +01:00
|
|
|
.with_state(Arc::new(RwLock::new(state)))
|
|
|
|
}
|
|
|
|
|
|
|
|
type SafeContext = Arc<RwLock<Context>>;
|
|
|
|
|
|
|
|
pub struct Context {
|
2023-12-23 03:14:18 +01:00
|
|
|
providers: Vec<Box<dyn NotificationProcessor<GuestBookPage>>>,
|
|
|
|
storage: Box<dyn StorageStrategy<GuestBookPage>>,
|
2023-12-22 23:37:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Context {
|
2023-12-23 03:14:18 +01:00
|
|
|
pub fn new(storage: Box<dyn StorageStrategy<GuestBookPage>>) -> Self {
|
2023-12-22 23:37:23 +01:00
|
|
|
Context { providers: Vec::new(), storage }
|
|
|
|
}
|
|
|
|
|
2023-12-23 03:14:18 +01:00
|
|
|
pub fn register(mut self, notifier: Box<dyn NotificationProcessor<GuestBookPage>>) -> Self {
|
2023-12-22 23:37:23 +01:00
|
|
|
self.providers.push(notifier);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-12-23 03:14:18 +01:00
|
|
|
async fn process(&self, x: &GuestBookPage) {
|
2023-12-22 23:37:23 +01:00
|
|
|
for p in self.providers.iter() {
|
|
|
|
p.process(x).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-23 03:15:06 +01:00
|
|
|
async fn send_suggestion(payload: Insertion, state: SafeContext) -> impl IntoResponse {
|
|
|
|
let mut hasher = Md5::new();
|
|
|
|
let id = payload.contact.clone().unwrap_or(Uuid::new_v4().to_string());
|
|
|
|
hasher.update(id.as_bytes());
|
|
|
|
let avatar = hasher.finalize();
|
|
|
|
let page = GuestBookPage {
|
|
|
|
avatar: format!("{:x}", avatar),
|
2023-12-23 03:24:45 +01:00
|
|
|
author: payload.author.map(|x| x.chars().take(25).collect()), // TODO don't hardcode char limits!
|
|
|
|
contact: payload.contact.clone().map(|x| x.chars().take(50).collect()),
|
2023-12-23 03:15:06 +01:00
|
|
|
body: payload.body,
|
|
|
|
date: Utc::now(),
|
|
|
|
url: match payload.contact {
|
|
|
|
None => None,
|
|
|
|
Some(c) => if c.starts_with("http") {
|
|
|
|
Some(c)
|
|
|
|
} else if c.contains('@') {
|
|
|
|
Some(format!("mailto:{}", c))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
2023-12-22 23:37:23 +01:00
|
|
|
let mut lock = state.write().await;
|
2023-12-23 03:14:18 +01:00
|
|
|
lock.process(&page).await;
|
|
|
|
match lock.storage.archive(page).await {
|
2023-12-22 23:37:23 +01:00
|
|
|
Ok(()) => (StatusCode::OK, Json(Acknowledgement::Sent("".into()))),
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, Json(Acknowledgement::Refused(e.to_string()))),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-23 03:14:18 +01:00
|
|
|
async fn send_suggestion_json(State(state): State<SafeContext>, Json(payload): Json<Insertion>) -> impl IntoResponse { send_suggestion(payload, state).await }
|
|
|
|
async fn send_suggestion_form(State(state): State<SafeContext>, Form(payload): Form<Insertion>) -> impl IntoResponse { send_suggestion(payload, state).await }
|
2023-12-22 23:37:23 +01:00
|
|
|
|
|
|
|
|
2023-12-23 03:14:18 +01:00
|
|
|
async fn get_suggestion(State(state): State<SafeContext>, Query(page): Query<PageOptions>) -> Result<Json<Vec<GuestBookPage>>, String> {
|
2023-12-22 23:37:23 +01:00
|
|
|
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 {
|
|
|
|
Ok(x) => Ok(Json(x)),
|
|
|
|
Err(e) => Err(e.to_string()),
|
|
|
|
}
|
|
|
|
}
|