This commit is contained in:
JOLIMAITRE Matthieu 2024-07-25 01:24:41 +02:00
commit 3f461e8375
6 changed files with 1571 additions and 0 deletions

210
src/main.rs Normal file
View file

@ -0,0 +1,210 @@
use std::fmt::Write;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use axum::{
extract::{Path, State},
response::IntoResponse,
routing::get,
Json, Router,
};
use chrono::prelude::*;
use common::{footer, head, header, style};
use maud::{html, Escaper, Markup, PreEscaped};
use serde::{Deserialize, Serialize};
use store::Store;
use tokio::net::TcpListener;
mod common;
mod store;
#[derive(Debug, Clone)]
struct MainState {
store: Arc<RwLock<Store>>,
}
#[tokio::main]
async fn main() -> Result<()> {
let store = Store::open("./store")?;
let store = Arc::new(RwLock::new(store));
let state = MainState { store };
let app = Router::new()
.route("/style.css", get(style))
.route("/topics", get(topics).post(post))
.route("/topic/:name", get(topic))
.route("/activity", get(activity))
.route("/", get(home))
.with_state(state);
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
let server = axum::serve(listener, app);
server.await?;
Ok(())
}
async fn home() -> Markup {
html!(
(head(""))
body {
content {
(header())
main {
"Bienvenue au recueil de Barnulf."
br;
"
Ce site contient une collection ouverte de réflexions
personnelles sur différents sujets relatifs à l'informatique.
"
}
(footer())
}
}
)
}
async fn topics(State(state): State<MainState>) -> impl IntoResponse {
let topics = state.store.read().unwrap().list().unwrap();
html!(
(head("sujets"))
body {
content {
(header())
main {
h2 { "Sujets" }
ul {
@for topic in topics {
li {
a href = { "/topic/" (topic) } { (topic) }
}
}
}
}
(footer())
}
}
)
}
async fn activity(State(state): State<MainState>) -> impl IntoResponse {
let topics = state.store.read().unwrap().activity().unwrap();
html!(
(head("activité"))
body {
content {
(header())
main {
h2 { "Activité" }
ul {
@for topic in topics {
li {
a href = { "/topic/" (topic) } { (topic) }
}
}
}
}
(footer())
}
}
)
}
async fn topic(Path(name): Path<String>, State(state): State<MainState>) -> impl IntoResponse {
let Ok(posts) = state.store.read().unwrap().get(&name) else {
return error("No such topic.");
};
html!(
(head(&name))
body {
content {
(header())
main {
h2 { (name) }
@for post in posts {
hr;
section { (PreEscaped({
let mut buffer = String::new();
write!(Escaper::new(&mut buffer), "{post}").unwrap();
buffer.replace('\n', "<br>")
})) }
}
}
(footer())
}
}
)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PostContent {
topic: String,
content: String,
author: String,
}
async fn post(
State(state): State<MainState>,
Json(PostContent {
topic,
content,
author,
}): Json<PostContent>,
) -> impl IntoResponse {
let mut store = state.store.write().unwrap();
let date = Utc::now().format("%d/%m/%Y");
let topic = sanithize_identifier(&topic);
let content = content.trim();
let content = format!("{content}\n\t~{author}, {date}");
store.insert(&topic, content).unwrap();
}
fn sanithize_identifier(input: &str) -> String {
let text = input.to_lowercase();
let replaces = [
('à', 'a'),
('â', 'a'),
('ä', 'a'),
('é', 'e'),
('è', 'e'),
('ê', 'e'),
('ë', 'e'),
('î', 'i'),
('ï', 'i'),
('ô', 'o'),
('ö', 'o'),
('û', 'u'),
('ü', 'u'),
];
text.chars()
.map(|c| {
for (from, to) in replaces {
if c == from {
return to;
}
}
c
})
.filter(char::is_ascii)
.collect()
}
fn error(message: impl ToString) -> Markup {
let message = message.to_string();
html!(
(head("Failure"))
body {
content {
(header())
main {
h2 { "Failure" }
br;
(message)
}
(footer())
}
}
)
}