mod flash; use axum::{ extract::{Extension, Form, Path, Query}, http::StatusCode, response::Html, routing::{get, get_service, post}, Router, Server, }; use axum_example_core::{ sea_orm::{Database, DatabaseConnection}, Mutation as MutationCore, Query as QueryCore, }; use entity::post; use flash::{get_flash_cookie, post_response, PostResponse}; use migration::{Migrator, MigratorTrait}; use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::{env, net::SocketAddr}; use tera::Tera; use tower::ServiceBuilder; use tower_cookies::{CookieManagerLayer, Cookies}; use tower_http::services::ServeDir; #[tokio::main] async fn start() -> anyhow::Result<()> { env::set_var("RUST_LOG", "debug"); tracing_subscriber::fmt::init(); dotenvy::dotenv().ok(); let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file"); let host = env::var("HOST").expect("HOST is not set in .env file"); let port = env::var("PORT").expect("PORT is not set in .env file"); let server_url = format!("{}:{}", host, port); let conn = Database::connect(db_url) .await .expect("Database connection failed"); Migrator::up(&conn, None).await.unwrap(); let templates = Tera::new(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")) .expect("Tera initialization failed"); // let state = AppState { templates, conn }; let app = Router::new() .route("/", get(list_posts).post(create_post)) .route("/:id", get(edit_post).post(update_post)) .route("/new", get(new_post)) .route("/delete/:id", post(delete_post)) .nest( "/static", get_service(ServeDir::new(concat!( env!("CARGO_MANIFEST_DIR"), "/static" ))) .handle_error(|error: std::io::Error| async move { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled internal error: {}", error), ) }), ) .layer( ServiceBuilder::new() .layer(CookieManagerLayer::new()) .layer(Extension(conn)) .layer(Extension(templates)), ); let addr = SocketAddr::from_str(&server_url).unwrap(); Server::bind(&addr).serve(app.into_make_service()).await?; Ok(()) } #[derive(Deserialize)] struct Params { page: Option, posts_per_page: Option, } #[derive(Deserialize, Serialize, Debug, Clone)] struct FlashData { kind: String, message: String, } async fn list_posts( Extension(ref templates): Extension, Extension(ref conn): Extension, Query(params): Query, cookies: Cookies, ) -> Result, (StatusCode, &'static str)> { let page = params.page.unwrap_or(1); let posts_per_page = params.posts_per_page.unwrap_or(5); let (posts, num_pages) = QueryCore::find_posts_in_page(conn, page, posts_per_page) .await .expect("Cannot find posts in page"); let mut ctx = tera::Context::new(); ctx.insert("posts", &posts); ctx.insert("page", &page); ctx.insert("posts_per_page", &posts_per_page); ctx.insert("num_pages", &num_pages); if let Some(value) = get_flash_cookie::(&cookies) { ctx.insert("flash", &value); } let body = templates .render("index.html.tera", &ctx) .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?; Ok(Html(body)) } async fn new_post( Extension(ref templates): Extension, ) -> Result, (StatusCode, &'static str)> { let ctx = tera::Context::new(); let body = templates .render("new.html.tera", &ctx) .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?; Ok(Html(body)) } async fn create_post( Extension(ref conn): Extension, form: Form, mut cookies: Cookies, ) -> Result { let form = form.0; MutationCore::create_post(conn, form) .await .expect("could not insert post"); let data = FlashData { kind: "success".to_owned(), message: "Post succcessfully added".to_owned(), }; Ok(post_response(&mut cookies, data)) } async fn edit_post( Extension(ref templates): Extension, Extension(ref conn): Extension, Path(id): Path, ) -> Result, (StatusCode, &'static str)> { let post: post::Model = QueryCore::find_post_by_id(conn, id) .await .expect("could not find post") .unwrap_or_else(|| panic!("could not find post with id {}", id)); let mut ctx = tera::Context::new(); ctx.insert("post", &post); let body = templates .render("edit.html.tera", &ctx) .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Template error"))?; Ok(Html(body)) } async fn update_post( Extension(ref conn): Extension, Path(id): Path, form: Form, mut cookies: Cookies, ) -> Result { let form = form.0; MutationCore::update_post_by_id(conn, id, form) .await .expect("could not edit post"); let data = FlashData { kind: "success".to_owned(), message: "Post succcessfully updated".to_owned(), }; Ok(post_response(&mut cookies, data)) } async fn delete_post( Extension(ref conn): Extension, Path(id): Path, mut cookies: Cookies, ) -> Result { MutationCore::delete_post(conn, id) .await .expect("could not delete post"); let data = FlashData { kind: "success".to_owned(), message: "Post succcessfully deleted".to_owned(), }; Ok(post_response(&mut cookies, data)) } pub fn main() { let result = start(); if let Some(err) = result.err() { println!("Error: {}", err); } }