diff --git a/src/common.rs b/src/common.rs index 1934d39..c791dab 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,9 +1,10 @@ use axum::response::IntoResponse; -use maud::{html, Markup}; +use maud::{html, Markup, DOCTYPE}; use stylist::{css, GlobalStyle}; pub fn head(title: &str) -> Markup { html!( + (DOCTYPE) head { title { "Recueil " (title) } link rel = "stylesheet" href = "/style.css"; @@ -71,6 +72,17 @@ pub async fn style() -> impl IntoResponse { color: wheat; } + label { + display: inline-block; + width: 8rem; + } + + textarea { + width: 100%; + height: 5rem; + box-sizing: border-box; + } + main { padding: 2rem; } diff --git a/src/main.rs b/src/main.rs index 6d76178..690e9e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use std::{ fmt::Write, + path::PathBuf, sync::{Arc, RwLock}, }; @@ -13,7 +14,7 @@ use axum::{ }; use chrono::prelude::*; use common::{footer, head, header, style}; -use maud::{html, Escaper, Markup, PreEscaped, DOCTYPE}; +use maud::{html, Escaper, Markup, PreEscaped}; use serde::{Deserialize, Serialize}; use store::Store; use tokio::net::TcpListener; @@ -22,6 +23,22 @@ use validator::{Validate, ValidationError}; mod common; mod store; +#[derive(Debug, clap::Parser)] +/// Arbre +struct Cmd { + #[arg(short, long, default_value_t = 8200)] + /// Port on which th server will listen. + port: u16, + + #[arg(short, long, default_value = "0.0.0.0")] + /// Hostname or address on which th server will listen. + address: String, + + #[arg(short, long, default_value = "./store")] + /// Path to the directory to use as storage for topics. + store: PathBuf, +} + #[derive(Debug, Clone)] struct MainState { store: Arc>, @@ -29,7 +46,13 @@ struct MainState { #[tokio::main] async fn main() -> Result<()> { - let store = Store::open("./store")?; + let Cmd { + address, + port, + store, + } = clap::Parser::parse(); + + let store = Store::open(store)?; let store = Arc::new(RwLock::new(store)); let state = MainState { store }; @@ -42,8 +65,10 @@ async fn main() -> Result<()> { .route("/", get(home)) .with_state(state); - let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap(); + let listener = TcpListener::bind((address.as_str(), port)).await.unwrap(); let server = axum::serve(listener, app); + + println!("Listening on http://{address}:{port}"); server.await?; Ok(()) @@ -51,7 +76,6 @@ async fn main() -> Result<()> { async fn home() -> Markup { html!( - (DOCTYPE) (head("")) body { content { @@ -73,7 +97,6 @@ async fn home() -> Markup { async fn topics(State(state): State) -> impl IntoResponse { let topics = state.store.read().unwrap().list().unwrap(); html!( - (DOCTYPE) (head("sujets")) body { content { @@ -97,7 +120,6 @@ async fn topics(State(state): State) -> impl IntoResponse { async fn activity(State(state): State) -> impl IntoResponse { let topics = state.store.read().unwrap().activity().unwrap(); html!( - (DOCTYPE) (head("activité")) body { content { @@ -124,7 +146,6 @@ async fn topic(Path(name): Path, State(state): State) -> impl }; html!( - (DOCTYPE) (head(&name)) body { content { @@ -164,11 +185,11 @@ fn validate_topic(topic: &str) -> Result<(), ValidationError> { Ok(()) } -async fn post(State(state): State, Form(post_content): Form) -> Response { +async fn post(State(state): State, Form(post): Form) -> Response { let mut store = state.store.write().unwrap(); let date = Utc::now().format("%d/%m/%Y"); - if post_content.validate().is_err() { + if post.validate().is_err() { return (StatusCode::BAD_REQUEST, "Bad input.").into_response(); } @@ -176,7 +197,7 @@ async fn post(State(state): State, Form(post_content): Form String { async fn create() -> impl IntoResponse { html!( - (DOCTYPE) (head("créer")) body { content { @@ -251,7 +271,6 @@ async fn create() -> impl IntoResponse { fn error(message: impl ToString) -> Markup { let message = message.to_string(); html!( - (DOCTYPE) (head("Failure")) body { content { diff --git a/src/store.rs b/src/store.rs index e86291d..76f1692 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,18 +1,18 @@ use std::{ fs::{self, OpenOptions}, io::{ErrorKind, Write}, + path::PathBuf, }; use anyhow::Result; #[derive(Debug)] pub struct Store { - path: String, + path: PathBuf, } impl Store { - pub fn open(path: impl ToString) -> Result { - let path = path.to_string(); + pub fn open(path: PathBuf) -> Result { fs::create_dir_all(&path)?; Ok(Self { path }) } @@ -44,8 +44,7 @@ impl Store { } pub fn get(&self, topic: &str) -> Result> { - let base = &self.path; - let path = format!("{base}/{topic}"); + let path = self.path.join(topic); let content = fs::read_to_string(path)?; Ok(content.split(SEPARATOR).map(String::from).collect()) } @@ -53,8 +52,7 @@ impl Store { pub fn insert(&mut self, topic: &str, content: String) -> Result<()> { let content = content.replace('\u{c}', " "); let content = content.trim(); - let base = &self.path; - let path = format!("{base}/{topic}"); + let path = self.path.join(topic); match OpenOptions::new().append(true).create(false).open(&path) { Ok(mut file) => write!(&mut file, "{SEPARATOR}\n{content}\n")?, Err(error) if error.kind() == ErrorKind::NotFound => drop(fs::write(path, content)), diff --git a/watch b/watch new file mode 100755 index 0000000..ca4c48f --- /dev/null +++ b/watch @@ -0,0 +1,3 @@ +#!/bin/sh + +cargo watch --clear --exec "run -- --address=localhost --port=8080"