mod answer; mod api; mod question; mod json_store; use axum::{ extract::{Path, Query, State}, http::{StatusCode, Uri}, response::{IntoResponse, Response}, routing::{delete, get, post, put}, Form, Json, Router, }; use serde::{Deserialize, Serialize}; use sqlx::{ self, postgres::{PgPool, Postgres}, Pool, }; use std::{ collections::HashMap, env::var, fs::File, io::{ErrorKind, Read, Seek, Write}, net::SocketAddr, path::PathBuf, sync::Arc, }; use json_store::Store; use tokio::sync::RwLock; // 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(), } } async fn db_connection() -> Result, sqlx::Error> { let url: String = format!( "postgres://{}:{}@{}:5432"/*{}*/, var("POSTGRES_USER").unwrap(), var("POSTGRES_PASSWORD").unwrap(), var("POSTGRES_HOST").unwrap(), // var("POSTGRES_DBNAME").unwrap() ); PgPool::connect(&url).await } #[tokio::main] async fn main() { let store: Arc> = Arc::new(RwLock::new(json_store::Store::new())); let ip: SocketAddr = SocketAddr::new([127, 0, 0, 1].into(), 3000); let listener: tokio::net::TcpListener = tokio::net::TcpListener::bind(ip).await.unwrap(); let apis = Router::new() .route("/question/:id", get(api::read_question)) .route("/questions", get(api::read_questions)) .route("/question", post(api::create_question)) .route("/question", put(api::update_question)) .route("/question/:id", delete(api::delete_question)) .route("/answer", post(api::create_answer)) .with_state(store); let app = Router::new() .nest("/api/v1", apis) .route("/", get(serve_file)) .route("/*path", get(serve_file)) .fallback(handler_404); let db: Pool = db_connection().await.unwrap(); sqlx::migrate!().run(&db).await.unwrap(); axum::serve(listener, app).await.unwrap(); }