init
This commit is contained in:
commit
3f461e8375
6 changed files with 1571 additions and 0 deletions
210
src/main.rs
Normal file
210
src/main.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue