1
0
Fork 0
forked from 2sys/shoutdotdev

fix "no nesting at root" bug with empty base_url

This commit is contained in:
Brent Schroeter 2025-03-11 22:22:30 -07:00
parent f6adc2ba88
commit 8b693d44ed
4 changed files with 75 additions and 87 deletions

41
Cargo.lock generated
View file

@ -38,21 +38,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.18"
@ -205,11 +190,10 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.18"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522"
checksum = "310c9bcae737a48ef5cdee3174184e6d548b292739ede61a1f955ef76a738861"
dependencies = [
"brotli",
"flate2",
"futures-core",
"memchr",
@ -481,27 +465,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "brotli"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]]
name = "bumpalo"
version = "3.16.0"

View file

@ -21,7 +21,7 @@ futures = "0.3.31"
uuid = { version = "1.11.0", features = ["js", "serde", "v4", "v7"] }
rand = "0.8.5"
tracing-subscriber = { version = "0.3.19", features = ["chrono", "env-filter"] }
tower-http = { version = "0.6.2", features = ["compression-br", "compression-gzip", "fs", "trace", "tracing"] }
tower-http = { version = "0.6.2", features = ["compression-gzip", "fs", "normalize-path", "trace"] }
tokio = { version = "1.42.0", features = ["macros", "rt-multi-thread", "tracing"] }
deadpool-diesel = { version = "0.6.1", features = ["postgres", "serde"] }
axum = { version = "0.8.1", features = ["macros"] }

View file

@ -23,11 +23,16 @@ mod worker;
use std::process::exit;
use axum::{extract::Request, middleware::map_request, ServiceExt};
use chrono::{TimeDelta, Utc};
use clap::{Parser, Subcommand};
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use email::SmtpOptions;
use tokio::time::sleep;
use tower::ServiceBuilder;
use tower_http::{
compression::CompressionLayer, normalize_path::NormalizePathLayer, trace::TraceLayer,
};
use tracing_subscriber::EnvFilter;
use crate::{
@ -129,7 +134,16 @@ async fn main() {
settings.port,
settings.base_path
);
axum::serve(listener, router).await.unwrap();
let app = ServiceExt::<Request>::into_make_service(
ServiceBuilder::new()
.layer(map_request(lowercase_uri_path))
.layer(TraceLayer::new_for_http())
.layer(CompressionLayer::new())
.layer(NormalizePathLayer::trim_trailing_slash())
.service(router),
);
axum::serve(listener, app).await.unwrap();
}
Commands::Worker { auto_loop_seconds } => {
if let Some(loop_seconds) = auto_loop_seconds {
@ -155,3 +169,17 @@ async fn main() {
}
}
}
async fn lowercase_uri_path<B>(mut request: Request<B>) -> Request<B> {
let path = request.uri().path().to_lowercase();
let path_and_query = match request.uri().query() {
Some(query) => format!("{}?{}", path, query),
None => path,
};
let builder =
axum::http::uri::Builder::from(request.uri().clone()).path_and_query(path_and_query);
*request.uri_mut() = builder
.build()
.expect("lowercasing URI path should not break it");
request
}

View file

@ -13,12 +13,7 @@ use diesel::{delete, dsl::insert_into, prelude::*, update};
use rand::{distributions::Uniform, Rng};
use regex::Regex;
use serde::Deserialize;
use tower::ServiceBuilder;
use tower_http::{
compression::CompressionLayer,
services::{ServeDir, ServeFile},
trace::TraceLayer,
};
use tower_http::services::{ServeDir, ServeFile};
use uuid::Uuid;
use crate::{
@ -46,48 +41,50 @@ const MAX_VERIFICATION_GUESSES: u32 = 100;
pub fn new_router(state: AppState) -> Router<()> {
let base_path = state.settings.base_path.clone();
Router::new().nest(
base_path.as_str(),
Router::new()
.route("/", get(landing_page))
.merge(v0_router::new_router(state.clone()))
.route("/teams", get(teams_page))
.route("/teams/{team_id}", get(team_page))
.route("/teams/{team_id}/projects", get(projects_page))
.route("/teams/{team_id}/projects/{project_id}", get(project_page))
.route(
"/teams/{team_id}/projects/{project_id}/update-enabled-channels",
post(update_enabled_channels),
)
.route("/teams/{team_id}/new-api-key", post(post_new_api_key))
.route("/teams/{team_id}/channels", get(channels_page))
.route("/teams/{team_id}/channels/{channel_id}", get(channel_page))
.route(
"/teams/{team_id}/channels/{channel_id}/update-channel",
post(update_channel),
)
.route(
"/teams/{team_id}/channels/{channel_id}/update-email-recipient",
post(update_channel_email_recipient),
)
.route(
"/teams/{team_id}/channels/{channel_id}/verify-email",
post(verify_email),
)
.route("/teams/{team_id}/new-channel", post(post_new_channel))
.route("/new-team", get(new_team_page))
.route("/new-team", post(post_new_team))
.nest("/auth", auth::new_router())
.fallback_service(
let app = Router::new()
.route("/", get(landing_page))
.merge(v0_router::new_router(state.clone()))
.route("/teams", get(teams_page))
.route("/teams/{team_id}", get(team_page))
.route("/teams/{team_id}/projects", get(projects_page))
.route("/teams/{team_id}/projects/{project_id}", get(project_page))
.route(
"/teams/{team_id}/projects/{project_id}/update-enabled-channels",
post(update_enabled_channels),
)
.route("/teams/{team_id}/new-api-key", post(post_new_api_key))
.route("/teams/{team_id}/channels", get(channels_page))
.route("/teams/{team_id}/channels/{channel_id}", get(channel_page))
.route(
"/teams/{team_id}/channels/{channel_id}/update-channel",
post(update_channel),
)
.route(
"/teams/{team_id}/channels/{channel_id}/update-email-recipient",
post(update_channel_email_recipient),
)
.route(
"/teams/{team_id}/channels/{channel_id}/verify-email",
post(verify_email),
)
.route("/teams/{team_id}/new-channel", post(post_new_channel))
.route("/new-team", get(new_team_page))
.route("/new-team", post(post_new_team))
.nest("/auth", auth::new_router())
.fallback_service(
ServeDir::new("static").not_found_service(ServeFile::new("static/_404.html")),
)
.with_state(state);
let app = {
if base_path == "" {
app
} else {
Router::new().nest(&base_path, app).fallback_service(
ServeDir::new("static").not_found_service(ServeFile::new("static/_404.html")),
)
.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(CompressionLayer::new()),
)
.with_state(state),
)
}
};
app
}
async fn landing_page(State(state): State<AppState>) -> impl IntoResponse {