support static routes

This commit is contained in:
David Westgate 2024-04-30 13:11:23 -07:00
parent 167d34731c
commit fe2311746b
6 changed files with 47 additions and 13 deletions

View File

@ -64,7 +64,6 @@ A test dataset of questions. Generated by [ChatGPT](https://chat.openai.com/)
### Looking ahead
These are a few things still to be added
#### Higher priority
* Support POST /answers
* Add some simple API curl examples to this README
* API documentation tooling (`utoipa`)
* API testing tooling (`swagger`)
@ -74,7 +73,6 @@ These are a few things still to be added
* Optimize flush/file writing: Write out the JSON in a pretty structure and avoid re-writing the whole file in cases when it can be avoid (like adding 1 item)
* Use a database alltogether instead of a json file for persistance
* Serve basic web page(s) to utilize all APIs
* Re-structure the application to serve API endpoint from an `api` directory, and generically serve web content from a `web` directory
* Optimize the put/update handler to support path or body identification, and to update only individual fields passed.
* Host application on my raspberry pi on the internet

View File

@ -93,6 +93,7 @@ pub async fn delete_question(
Err(e) => (StatusCode::NOT_FOUND, e).into_response(),
}
}
/// Create an Answer
/// # Parameters
/// `answer_dto` Form URL encoded answer DTO
@ -100,7 +101,6 @@ pub async fn delete_question(
/// Status Created 201 and the created answer upon success
/// Status Conflict 409 and message if answer with ID already exists
/// Status Unprocessable Entity 422 is returned (implicitlly) for a malformed body
pub async fn create_answer(
State(store): State<Arc<RwLock<Store>>>,
Form(answer_dto): Form<AnswerDTO>,

View File

@ -4,7 +4,7 @@ mod question;
mod store;
use axum::{
extract::{Path, Query, State},
http::StatusCode,
http::{StatusCode, Uri},
response::{IntoResponse, Response},
routing::{delete, get, post, put},
Form, Json, Router,
@ -13,22 +13,40 @@ use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fs::File,
io::{ErrorKind, Seek, Write},
io::{ErrorKind, Read, Seek, Write},
net::SocketAddr,
path::PathBuf,
sync::Arc,
};
use store::Store;
use tokio::sync::RwLock;
async fn handle() -> Response {
(StatusCode::OK, "Visiting the root").into_response()
}
// generic handler for any not supported route/method combination
async fn handler_404() -> Response {
(StatusCode::NOT_FOUND, "404 Not Found").into_response()
}
// generic handler to serve static for non-api routes
async fn serve_file(uri: Uri) -> impl IntoResponse {
let mut path_buf: PathBuf = PathBuf::from("./src/static/").join(uri.path().trim_start_matches('/'));
if path_buf.is_dir() {
path_buf.push("index.html")
}
match File::open(path_buf) {
Ok(mut file) => {
let mut contents = Vec::new();
match file.read_to_end(&mut contents) {
Ok(_) => axum::response::Response::builder()
.status(StatusCode::OK)
.body(axum::body::Body::from(contents))
.unwrap(),
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Server Error").into_response(),
}
}
Err(_) => (StatusCode::NOT_FOUND, "404 Not Found").into_response(),
}
}
#[tokio::main]
async fn main() {
let store: Arc<RwLock<Store>> = Arc::new(RwLock::new(store::Store::new()));
@ -41,9 +59,11 @@ async fn main() {
.route("/question", put(api::update_question))
.route("/question/:id", delete(api::delete_question))
.route("/answer", post(api::create_answer))
.with_state(store)
.with_state(store);
let app = Router::new()
.nest("/api/v1", apis)
.route("/", get(serve_file))
.route("/*path", get(serve_file))
.fallback(handler_404);
let app = Router::new().route("/", get(handle)).merge(apis);
axum::serve(listener, app).await.unwrap();
}

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>Welcome again</title>
</head>
<body>
Hello cats!
</body>
</html>

BIN
src/static/cats/warhol.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

8
src/static/index.html Normal file
View File

@ -0,0 +1,8 @@
<html>
<head>
<title>Welcome</title>
</head>
<body>
Hello world!
</body>
</html>