Skip to content

Commit 0b9739e

Browse files
chore: wip switch to axum
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
1 parent 32e7cb0 commit 0b9739e

13 files changed

Lines changed: 1524 additions & 454 deletions

File tree

Cargo.lock

Lines changed: 226 additions & 430 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,17 @@ tracing-subscriber={version="0.3", features=["env-filter"]}
4646
ahash="0.8"
4747

4848
# web
49-
poem={version="3.1", default-features=false, features=[
50-
"embed",
51-
"cookie",
52-
"compression",
53-
"tower-compat",
54-
]}
55-
poem-openapi={version="5.1", default-features=false, features=["chrono"]}
49+
axum="0.8"
50+
axum-extra={version="0.12", default-features=false, features=["cookie"]}
5651
tower={version="0.5", default-features=false, features=["limit"]}
52+
tower-http={version="0.6", default-features=false, features=[
53+
"cors",
54+
"compression-zstd",
55+
"set-header",
56+
]}
57+
aide={version="0.15", default-features=false, features=["axum"]}
58+
schemars={version="1.2", features=["derive"]}
59+
5760
ua-parser="0.2"
5861
rust-embed={version="8.9", features=["mime-guess"]}
5962
reqwest={version="0.13", default-features=false, features=["json", "stream", "charset", "rustls"]}

src/app/models.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt::Display;
22

33
use chrono::{DateTime, Utc};
4-
use poem_openapi::Enum;
4+
use schemars::JsonSchema;
55
use serde::{Deserialize, Serialize};
66

77
#[derive(Debug, Clone)]
@@ -46,8 +46,8 @@ pub struct User {
4646
pub projects: Vec<String>,
4747
}
4848

49-
#[derive(Debug, Enum, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Default)]
50-
#[oai(rename_all = "snake_case")]
49+
#[derive(Debug, JsonSchema, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Default)]
50+
#[serde(rename_all = "snake_case")]
5151
pub enum UserRole {
5252
#[serde(rename = "admin")]
5353
Admin,

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod cli;
33
pub mod config;
44
pub mod utils;
55
pub mod web;
6+
pub mod web_axum;

src/web/webext.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,6 @@ impl<T, E: Display> PoemErrExt<T> for Result<T, E> {
6262
}
6363
}
6464

65-
pub struct EmbeddedFilesEndpoint<E: RustEmbed + Send + Sync>(PhantomData<E>);
66-
67-
impl<E: RustEmbed + Send + Sync> Default for EmbeddedFilesEndpoint<E> {
68-
fn default() -> Self {
69-
Self::new()
70-
}
71-
}
72-
73-
impl<E: RustEmbed + Send + Sync> EmbeddedFilesEndpoint<E> {
74-
pub fn new() -> Self {
75-
Self(PhantomData)
76-
}
77-
}
78-
7965
pub type ApiResult<T> = poem::Result<T, poem::Error>;
8066

8167
#[derive(Object, Serialize)]
@@ -96,6 +82,20 @@ impl EmptyResponse {
9682
}
9783
}
9884

85+
pub struct EmbeddedFilesEndpoint<E: RustEmbed + Send + Sync>(PhantomData<E>);
86+
87+
impl<E: RustEmbed + Send + Sync> Default for EmbeddedFilesEndpoint<E> {
88+
fn default() -> Self {
89+
Self::new()
90+
}
91+
}
92+
93+
impl<E: RustEmbed + Send + Sync> EmbeddedFilesEndpoint<E> {
94+
pub fn new() -> Self {
95+
Self(PhantomData)
96+
}
97+
}
98+
9999
impl<E: RustEmbed + Send + Sync> Endpoint for EmbeddedFilesEndpoint<E> {
100100
type Output = Response;
101101

src/web_axum/mod.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
pub mod routes;
2+
pub mod session;
3+
pub mod webext;
4+
5+
use std::net::SocketAddr;
6+
use std::sync::Arc;
7+
8+
use crate::app::models::Event;
9+
use crate::{app::Liwan, web_axum::webext::StaticFile};
10+
use axum::Extension;
11+
use axum::handler::Handler;
12+
use axum::http::{Method, header};
13+
use axum::response::IntoResponse;
14+
use axum::{Router, error_handling::HandleErrorLayer, http::StatusCode, routing::IntoMakeService};
15+
use routes::{dashboard_service, event_service};
16+
use std::sync::mpsc::Sender;
17+
use tower::ServiceBuilder;
18+
use tower_http::compression::CompressionLayer;
19+
use tower_http::cors::{Any, CorsLayer};
20+
use tower_http::set_header::SetResponseHeaderLayer;
21+
22+
use aide::{
23+
axum::{
24+
ApiRouter, IntoApiResponse,
25+
routing::{get, post},
26+
},
27+
openapi::{Info, OpenApi},
28+
};
29+
30+
pub use session::SessionUser;
31+
32+
use anyhow::{Context, Result};
33+
use rust_embed::RustEmbed;
34+
35+
#[derive(RustEmbed, Clone)]
36+
#[folder = "./web/dist"]
37+
struct Files;
38+
39+
#[derive(RustEmbed, Clone)]
40+
#[folder = "./tracker"]
41+
struct Script;
42+
43+
#[derive(Clone)]
44+
pub struct State {
45+
pub app: Arc<Liwan>,
46+
pub events: Sender<Event>,
47+
}
48+
49+
pub async fn start_webserver(app: Arc<Liwan>, events: Sender<Event>) -> Result<()> {
50+
let router = Router::new();
51+
52+
let event_cors = CorsLayer::new().allow_methods([Method::POST]).allow_origin(Any).allow_credentials(false);
53+
let script_cors = CorsLayer::new().allow_methods([Method::GET]).allow_origin(Any).allow_credentials(false);
54+
55+
let set_headers = ServiceBuilder::new()
56+
.layer(SetResponseHeaderLayer::if_not_present(header::X_FRAME_OPTIONS, "DENY"))
57+
.layer(SetResponseHeaderLayer::if_not_present(header::X_CONTENT_TYPE_OPTIONS, "nosniff"))
58+
.layer(SetResponseHeaderLayer::if_not_present(header::X_XSS_PROTECTION, "1; mode=block"))
59+
.layer(SetResponseHeaderLayer::if_not_present(
60+
header::CONTENT_SECURITY_POLICY,
61+
"default-src 'self' data: 'unsafe-inline'; img-src 'self' data: https://*",
62+
))
63+
.layer(SetResponseHeaderLayer::if_not_present(header::REFERRER_POLICY, "same-origin"));
64+
65+
let dashboard = ApiRouter::new()
66+
.merge(routes::admin::router())
67+
.merge(routes::auth::router())
68+
.merge(routes::dashboard::router());
69+
70+
let router = ApiRouter::new()
71+
.nest("/api/event", routes::event::router().layer(event_cors))
72+
.nest("/api/dashboard", dashboard)
73+
.route_service("/script.js", StaticFile::<Script>::new("script.min.js").layer(script_cors))
74+
.layer(CompressionLayer::new())
75+
.layer(set_headers)
76+
.with_state(State { app: app.clone(), events });
77+
78+
match app.onboarding.token()? {
79+
Some(onboarding) => {
80+
let get_started = format!("{}/setup?t={}", app.config.base_url, onboarding);
81+
tracing::info!("It looks like you're running Liwan for the first time!");
82+
tracing::info!("You can get started by visiting: {get_started}");
83+
tracing::info!("To see all available commands, run `liwan --help`");
84+
}
85+
_ => {
86+
tracing::info!("Liwan is running on {}", app.config.base_url);
87+
}
88+
}
89+
90+
let mut api = OpenApi::default();
91+
api.info = Info { description: Some("an example API".to_string()), ..Info::default() };
92+
93+
let listener = tokio::net::TcpListener::bind(("0.0.0.0", app.config.port)).await.unwrap();
94+
let server = router.finish_api(&mut api).into_make_service_with_connect_info::<SocketAddr>();
95+
axum::serve(listener, server).await.context("server exited unexpectedly")
96+
}

0 commit comments

Comments
 (0)