222 lines
6.2 KiB
Rust

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<usize>,
posts_per_page: Option<usize>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
struct FlashData {
kind: String,
message: String,
}
async fn list_posts(
Extension(ref templates): Extension<Tera>,
Extension(ref conn): Extension<DatabaseConnection>,
Query(params): Query<Params>,
cookies: Cookies,
) -> Result<Html<String>, (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::<FlashData>(&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<Tera>,
) -> Result<Html<String>, (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<DatabaseConnection>,
form: Form<post::Model>,
mut cookies: Cookies,
) -> Result<PostResponse, (StatusCode, &'static str)> {
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<Tera>,
Extension(ref conn): Extension<DatabaseConnection>,
Path(id): Path<i32>,
) -> Result<Html<String>, (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<DatabaseConnection>,
Path(id): Path<i32>,
form: Form<post::Model>,
mut cookies: Cookies,
) -> Result<PostResponse, (StatusCode, String)> {
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<DatabaseConnection>,
Path(id): Path<i32>,
mut cookies: Cookies,
) -> Result<PostResponse, (StatusCode, &'static str)> {
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))
}