From be6c1b5701d3f2fae0f6a8f785b2b8920ddc5753 Mon Sep 17 00:00:00 2001 From: Fergus Molloy Date: Sat, 19 Jul 2025 01:29:32 +0100 Subject: [PATCH] implement not found for ui --- Cargo.lock | 1 + Cargo.toml | 1 + Makefile | 1 + flake.nix | 1 + src/routes/app.rs | 12 ++++++++++++ src/routes/mod.rs | 29 +++++++++++++++++++++++++++-- tests/endpoint_test.rs | 23 ++++++++++++++++++++--- ui/src/App.vue | 2 +- ui/src/router/index.ts | 6 ++++++ ui/src/views/NotFoundView.vue | 16 ++++++++++++++++ 10 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 src/routes/app.rs create mode 100644 ui/src/views/NotFoundView.vue diff --git a/Cargo.lock b/Cargo.lock index 913dc5e..dfc8dfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,6 +492,7 @@ version = "0.1.0" dependencies = [ "axum", "clap", + "http-body", "http-body-util", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index d560d15..11df614 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] axum = "0.8.4" clap = { version = "4.5.41", features = ["derive"] } +http-body = "1.0.1" http-body-util = "0.1.3" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" diff --git a/Makefile b/Makefile index cb71118..65b916d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ dist: ui cd ui && make dist + cp -r ui/dist dist target/debug/nuchat: src dist cargo build diff --git a/flake.nix b/flake.nix index 118fc07..b99313c 100644 --- a/flake.nix +++ b/flake.nix @@ -32,6 +32,7 @@ pnpm just openssl + watchexec ]; }; diff --git a/src/routes/app.rs b/src/routes/app.rs new file mode 100644 index 0000000..7751c53 --- /dev/null +++ b/src/routes/app.rs @@ -0,0 +1,12 @@ +use axum::{ + body::Body, + extract::Request, + http::{Request, Response, StatusCode}, + response::IntoResponse, + routing::MethodRouter, +}; + +use http_body_util::combinators::BoxBody; +use tower::{BoxError, Service, service_fn}; +use tower_http::services::{ServeDir, ServeFile}; +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index b57e951..be14d92 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,6 +1,11 @@ use std::sync::mpsc; use axum::Router; +use axum::body::Body; +use axum::extract::Request; +use axum::http::StatusCode; +use axum::middleware::{Next, from_fn}; +use axum::response::Response; use axum::routing::{get, post}; use tower_http::services::{ServeDir, ServeFile}; @@ -11,13 +16,33 @@ use healthcheck::healthcheck; use shutdown::shutdown; pub fn app() -> (Router, mpsc::Receiver) { - let serve_dir = ServeDir::new("dist").not_found_service(ServeFile::new("dist/index.html")); + let serve_dir = || ServeDir::new("dist").not_found_service(ServeFile::new("dist/index.html")); let (tx, rx) = mpsc::channel(); ( Router::new() .route("/api/healthcheck", get(healthcheck)) .route("/api/shutdown", post(move || shutdown(tx.clone()))) - .fallback_service(serve_dir), + .fallback_service(serve_dir()) + .layer(from_fn(not_found_layer)), rx, ) } + +async fn not_found_layer(req: Request, next: Next) -> Response { + let path: String = req.uri().path().to_owned(); + let resp = next.run(req).await; + + if resp.status() != StatusCode::NOT_FOUND { + resp + } else if path.starts_with("/api/") { + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from(String::from("Not Found"))) + .unwrap() + } else { + let (mut parts, body) = resp.into_parts(); + parts.status = StatusCode::OK; + + Response::from_parts(parts, body) + } +} diff --git a/tests/endpoint_test.rs b/tests/endpoint_test.rs index 145b7f8..cd4c8e8 100644 --- a/tests/endpoint_test.rs +++ b/tests/endpoint_test.rs @@ -42,13 +42,30 @@ async fn root_returns_spa() { } #[tokio::test] -async fn unkown_fallsback_to_spa() { +async fn unkown_url_fallback_to_spa() { let (app, _) = nuchat::app(); + let response = app + .clone() + .oneshot( + Request::builder() + .uri("/asdfasdfa") // unknown url + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + + let body = response.into_body().collect().await.unwrap().to_bytes(); + let body = String::from_utf8(body.into_iter().collect()).unwrap(); + assert!(body.starts_with("")); + let response = app .oneshot( Request::builder() - .uri("/asdfasdfa") // unknown url + .uri("/api/asdfasdfa") // unknown url .body(Body::empty()) .unwrap(), ) @@ -59,5 +76,5 @@ async fn unkown_fallsback_to_spa() { let body = response.into_body().collect().await.unwrap().to_bytes(); let body = String::from_utf8(body.into_iter().collect()).unwrap(); - assert!(body.starts_with("")); + assert!(body.starts_with("Not Found")); } diff --git a/ui/src/App.vue b/ui/src/App.vue index da3fe35..f61748f 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -13,7 +13,7 @@ import HelloWorld from './components/HelloWorld.vue' diff --git a/ui/src/router/index.ts b/ui/src/router/index.ts index 3e49915..7ff3ce3 100644 --- a/ui/src/router/index.ts +++ b/ui/src/router/index.ts @@ -1,5 +1,6 @@ import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' +import NotFoundView from '../views/NotFoundView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -17,6 +18,11 @@ const router = createRouter({ // which is lazy-loaded when the route is visited. component: () => import('../views/AboutView.vue'), }, + { + path: '/:path(.*)*', + name: 'not-found', + component: NotFoundView, + }, ], }) diff --git a/ui/src/views/NotFoundView.vue b/ui/src/views/NotFoundView.vue new file mode 100644 index 0000000..5a8b66b --- /dev/null +++ b/ui/src/views/NotFoundView.vue @@ -0,0 +1,16 @@ + + +