mod flash; mod post; mod setup; use axum::{ error_handling::HandleErrorExt, extract::{Extension, Form, Path, Query}, http::StatusCode, response::Html, routing::{get, post, service_method_routing}, AddExtensionLayer, Router, Server, }; use flash::{get_flash_cookie, post_response, PostResponse}; use post::Entity as Post; use sea_orm::{prelude::*, Database, QueryOrder, Set}; 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 main() -> anyhow::Result<()> { env::set_var("RUST_LOG", "debug"); env_logger::init(); dotenv::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"); let _ = setup::create_post_table(&conn).await; 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", service_method_routing::get(ServeDir::new(concat!( env!("CARGO_MANIFEST_DIR"), "/static" ))) .handle_error(|error: std::io::Error| { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled internal error: {}", error), ) }), ) .layer( ServiceBuilder::new() .layer(CookieManagerLayer::new()) .layer(AddExtensionLayer::new(conn)) .layer(AddExtensionLayer::new(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 paginator = Post::find() .order_by_asc(post::Column::Id) .paginate(conn, posts_per_page); let num_pages = paginator.num_pages().await.ok().unwrap(); let posts = paginator .fetch_page(page - 1) .await .expect("could not retrieve posts"); 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 model = form.0; post::ActiveModel { title: Set(model.title.to_owned()), text: Set(model.text.to_owned()), ..Default::default() } .save(conn) .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 = Post::find_by_id(id) .one(conn) .await .expect("could not find post") .unwrap(); 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 model = form.0; post::ActiveModel { id: Set(id), title: Set(model.title.to_owned()), text: Set(model.text.to_owned()), } .save(conn) .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 { let post: post::ActiveModel = Post::find_by_id(id) .one(conn) .await .unwrap() .unwrap() .into(); post.delete(conn).await.unwrap(); let data = FlashData { kind: "success".to_owned(), message: "Post succcessfully deleted".to_owned(), }; Ok(post_response(&mut cookies, data)) }