implement not found for ui
All checks were successful
Cargo / build (push) Successful in 34s
Cargo / ui (push) Successful in 38s
Cargo / check (push) Successful in 37s
Cargo / test (push) Successful in 38s

This commit is contained in:
2025-07-19 01:29:32 +01:00
parent dfdcad7b1f
commit be6c1b5701
10 changed files with 86 additions and 6 deletions

1
Cargo.lock generated
View File

@ -492,6 +492,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"clap", "clap",
"http-body",
"http-body-util", "http-body-util",
"serde", "serde",
"serde_json", "serde_json",

View File

@ -6,6 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
axum = "0.8.4" axum = "0.8.4"
clap = { version = "4.5.41", features = ["derive"] } clap = { version = "4.5.41", features = ["derive"] }
http-body = "1.0.1"
http-body-util = "0.1.3" http-body-util = "0.1.3"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.140"

View File

@ -1,5 +1,6 @@
dist: ui dist: ui
cd ui && make dist cd ui && make dist
cp -r ui/dist dist
target/debug/nuchat: src dist target/debug/nuchat: src dist
cargo build cargo build

View File

@ -32,6 +32,7 @@
pnpm pnpm
just just
openssl openssl
watchexec
]; ];
}; };

12
src/routes/app.rs Normal file
View File

@ -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};
}

View File

@ -1,6 +1,11 @@
use std::sync::mpsc; use std::sync::mpsc;
use axum::Router; 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 axum::routing::{get, post};
use tower_http::services::{ServeDir, ServeFile}; use tower_http::services::{ServeDir, ServeFile};
@ -11,13 +16,33 @@ use healthcheck::healthcheck;
use shutdown::shutdown; use shutdown::shutdown;
pub fn app() -> (Router, mpsc::Receiver<bool>) { pub fn app() -> (Router, mpsc::Receiver<bool>) {
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(); let (tx, rx) = mpsc::channel();
( (
Router::new() Router::new()
.route("/api/healthcheck", get(healthcheck)) .route("/api/healthcheck", get(healthcheck))
.route("/api/shutdown", post(move || shutdown(tx.clone()))) .route("/api/shutdown", post(move || shutdown(tx.clone())))
.fallback_service(serve_dir), .fallback_service(serve_dir())
.layer(from_fn(not_found_layer)),
rx, 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)
}
}

View File

@ -42,13 +42,30 @@ async fn root_returns_spa() {
} }
#[tokio::test] #[tokio::test]
async fn unkown_fallsback_to_spa() { async fn unkown_url_fallback_to_spa() {
let (app, _) = nuchat::app(); 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("<!DOCTYPE html>"));
let response = app let response = app
.oneshot( .oneshot(
Request::builder() Request::builder()
.uri("/asdfasdfa") // unknown url .uri("/api/asdfasdfa") // unknown url
.body(Body::empty()) .body(Body::empty())
.unwrap(), .unwrap(),
) )
@ -59,5 +76,5 @@ async fn unkown_fallsback_to_spa() {
let body = response.into_body().collect().await.unwrap().to_bytes(); let body = response.into_body().collect().await.unwrap().to_bytes();
let body = String::from_utf8(body.into_iter().collect()).unwrap(); let body = String::from_utf8(body.into_iter().collect()).unwrap();
assert!(body.starts_with("<!DOCTYPE html>")); assert!(body.starts_with("Not Found"));
} }

View File

@ -13,7 +13,7 @@ import HelloWorld from './components/HelloWorld.vue'
<nav> <nav>
<RouterLink to="/">Home</RouterLink> <RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink> <RouterLink to="/about">About</RouterLink>
<RouterLink to="/test">Test</RouterLink> <RouterLink to="/asdf">asdfasdf</RouterLink>
</nav> </nav>
</div> </div>
</header> </header>

View File

@ -1,5 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue' import HomeView from '../views/HomeView.vue'
import NotFoundView from '../views/NotFoundView.vue'
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -17,6 +18,11 @@ const router = createRouter({
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue'), component: () => import('../views/AboutView.vue'),
}, },
{
path: '/:path(.*)*',
name: 'not-found',
component: NotFoundView,
},
], ],
}) })

View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()
let path = "/"
for( let part of route.params.path ){
path += part + "/"
}
path = path.slice(0,-1)
</script>
<template>
<main>
Could not find page at {{ path }}
</main>
</template>